Skip to content

Commit

Permalink
Merge pull request #2 from strass/updates
Browse files Browse the repository at this point in the history
LoadCredentials compatibility
  • Loading branch information
strass authored Jul 30, 2024
2 parents 7578f78 + 97ec0cc commit 4ae67c3
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 22 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ or for generic secrets:

### Example Requests

#### Via CLI

- To get an AppRole Role ID:
```
echo -n "myservice/role-id" | nc -U /run/vault-credentials.socket
Expand All @@ -78,7 +80,12 @@ or for generic secrets:
```
echo -n "secrets/app_secret/password" | nc -U /run/vault-credentials.socket
```

#### Via Systemd Unit
LoadCredential=services.%N.secret:/run/vault-credentials.socket

## Acknowledgments

- [Damomurf's systemd-credentials-vault](https://github.com/damomurf/systemd-credentials-vault): Provided a template and initial inspiration for extending this Golang application.
- [Medium article by Umglurf](https://medium.com/@umglurf/using-systemd-credentials-to-pass-secrets-from-hashicorp-vault-to-systemd-services-928f0e804518): Offered a Python script that was integrated into the original repository and guided further development.
- [Damomurf's systemd-credentials-vault](https://github.com/damomurf/systemd-credentials-vault): Provided a template and initial inspiration for extending this Golang application to work with LoadCredentials
- [Medium article by Umglurf](https://medium.com/@umglurf/using-systemd-credentials-to-pass-secrets-from-hashicorp-vault-to-systemd-services-928f0e804518): Offered a Python script that was integrated into the original repository and guided further development.
- [arianvp's systemd creds](https://github.com/arianvp/systemd-creds) has an example of how LoadCredentials sends its Network Addr
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/hashicorp/vault/api v1.14.0
github.com/hashicorp/vault/api/auth/approle v0.7.0
github.com/pkg/errors v0.9.1
github.com/spatialcurrent/go-stringify v0.0.0-20220308153339-0abf902cfee4
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/spatialcurrent/go-stringify v0.0.0-20220308153339-0abf902cfee4 h1:9Pg1iV1PATa6xYxQAlSGsRYwHh1OEnp7Lphaf+MV6bs=
github.com/spatialcurrent/go-stringify v0.0.0-20220308153339-0abf902cfee4/go.mod h1:QOkIEQRq/x7eI60dZf97Z1kCCICTXEoRCcHPbtq0kIQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
Expand Down
83 changes: 63 additions & 20 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"regexp"
"strings"
"syscall"
"time"

"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/api/auth/approle"
"github.com/pkg/errors"
"github.com/spatialcurrent/go-stringify/pkg/stringify"
"golang.org/x/sync/errgroup"
// reuse "github.com/portmapping/go-reuse"
)

type VaultCredentialServer struct {
Expand All @@ -27,7 +29,6 @@ type VaultCredentialServer struct {
}

func NewVaultCredentialServer(config *Config) (*VaultCredentialServer, error) {

apiConfig := api.DefaultConfig()
if config.VaultServer != nil {
apiConfig.Address = *config.VaultServer
Expand Down Expand Up @@ -81,7 +82,6 @@ func (vcs *VaultCredentialServer) Run(ctx context.Context) error {

func (vcs *VaultCredentialServer) startServer(ctx context.Context) error {
for {
// This only accepts once and halts after the 2nd request
conn, err := vcs.socket.Accept()
if err != nil {
select {
Expand All @@ -97,36 +97,49 @@ func (vcs *VaultCredentialServer) startServer(ctx context.Context) error {
}

func (vcs *VaultCredentialServer) handleConnection(ctx context.Context, conn net.Conn) {
defer conn.Close()
unixAddr, ok := conn.RemoteAddr().(*net.UnixAddr)
if !ok {
log.Printf("Failed to get peer name: %s", unixAddr.Name)
return
}

var err error
var value string

if unixAddr.Name == "@" {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Printf("failed to read from connection: %v", err)
return
}
defer conn.Close()

request := string(buf[:n])
parts := strings.Split(request, "/")
if len(parts) < 2 {
log.Printf("invalid request: %s", request)
value = string(buf[:n])
} else {
_, credential, ok := parsePeerName(unixAddr.Name)

if !ok {
log.Printf("Failed to parse peer name: %s", unixAddr.Name)
return
}

value = credential
}

parts := strings.Split(value, ".")
service, credential := parts[0], parts[1]

var value string
switch credential {
case "role-id":
value, err = vcs.getVaultAppRoleID(service)
case "secret-id":
value, err = vcs.getVaultAppRoleSecretID(service)
case "creds":
mount, _, roleName := service, credential, parts[2]
value, err = vcs.createVaultDatabaseCreds(mount, roleName)
default:
if len(parts) < 3 {
log.Printf("invalid request: %s", request)
return
}
mount, secretName, key := parts[0], parts[1], parts[2]

mount, secretName, key := service, credential, parts[2]
value, err = vcs.getVaultServerSecret(mount, secretName, key)
}

Expand All @@ -138,18 +151,29 @@ func (vcs *VaultCredentialServer) handleConnection(ctx context.Context, conn net
conn.Write([]byte(value))
}

// parsePeerName parses the peer name of a unix socket connection as per the
// documentation of LoadCredential=
func parsePeerName(s string) (string, string, bool) {
print("\n", s, "\n")
// NOTE: Apparently in Go abtract socket names are prefixed with @ instead of 0x00
matches := regexp.MustCompile("^@.*/unit/(.*)/(.*)$").FindStringSubmatch(s)
if matches == nil {
return "", "", false
}
unitName := matches[1]
credID := matches[2]

return unitName, credID, true
}

func (vcs *VaultCredentialServer) vaultLogin(ctx context.Context) error {
roleID, secretID := vcs.getVaultCredentials()

if roleID == "" {
return errors.New("Role ID not provided")
}

appRoleAuth, err := approle.NewAppRoleAuth(
roleID,
secretID,
approle.WithWrappingToken(), // Only required if the secret ID is response-wrapped.
)
appRoleAuth, err := approle.NewAppRoleAuth(roleID, secretID)
if err != nil {
return fmt.Errorf("failed to create AppRoleAuth: %v", err)
}
Expand Down Expand Up @@ -214,6 +238,25 @@ func (vcs *VaultCredentialServer) getVaultServerSecret(mount, secretName string,
return value, nil
}

var stringer = stringify.NewStringer("-", true, false, false)

func (vcs *VaultCredentialServer) createVaultDatabaseCreds(mount string, role string) (string, error) {
path := fmt.Sprintf("%s/creds/%s", mount, role)

secret, err := vcs.client.Logical().Read(path)
if err != nil {
return "", fmt.Errorf("failed to create auth for %v: %v", mount, err)
}

value, err := json.Marshal(secret.Data)
print(value)
if err != nil {
return "", fmt.Errorf("could not read secret data: %v", role)
}

return fmt.Sprintf("%s", value), nil
}

func (vcs *VaultCredentialServer) getVaultCredentials() (string, *approle.SecretID) {
secretID := &approle.SecretID{FromFile: vcs.config.SecretIdPath}

Expand Down

0 comments on commit 4ae67c3

Please sign in to comment.