diff --git a/README.md b/README.md index e743f48d..39201d4d 100644 --- a/README.md +++ b/README.md @@ -1191,14 +1191,34 @@ Depending on which one is chosen with the `INFISICAL_AUTH_METHOD` environment va This provider allows retrieval of secrets from [Delinea SecretSever](https://delinea.com/products/secret-server) using their [REST API](https://docs.delinea.com/online-help/secret-server/api-scripting/rest-api/index.htm) -Environment variables: +#### Configuration -- `SECRETSERVER_TOKEN`: The API Token to authenticate with. Can be created using their [OAuth Endpoint](https://updates.thycotic.net/secretserver/restapiguide/OAuth/) -- `SECRETSERVER_URL`: The URL to the SecretServer instance. +For on-prem instances set `TSS_SERVER_URL`. For cloud use set `TSS_TLD` to the top level domain and `TSS_TENANT` to your tenant id. If `TSS_SERVER_URL` is set other connection variables are ignored. -Examples: +#### Authentication + +Authentication is done via environment variables: + +- `TSS_USERNAME`: username to authenticate with +- `TSS_PASSWORD`: password to authenticate with +- `TSS_DOMAIN`: optional domain for the user + +Alternatively you can provide an OAuth token directly via `TSS_TOKEN`. If you do all other authentication environment variables are ignored. + +#### Parameters + +You can disable ssl certificate verification by setting `ssl_verify=false` in the URLs +query. + +#### Examples + +- `ref+tss://12345#/password`: gets the `password` field of the secret with id `12345` +- `ref+tss://secret-name/password`: gets the `password` field of the secret with the name `secret-name`. The name has to uniquely identify the secret + + +#### Limitations -- `ref+secretserver://12345/password`: gets the `password` field of the secret with id `12345` from the SecretServer running at the URL provdied in `SECRETSERVER_URL` +The content of file fields, like certificates can't be retrieved. They will be replaced with the string `*** Not Valid For Display ***`. ## Advanced Usages diff --git a/go.mod b/go.mod index c49f55ee..f9684c43 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 + github.com/DelineaXPM/tss-sdk-go/v3 v3.0.1 github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 github.com/a8m/envsubst v1.4.3 github.com/antchfx/jsonquery v1.3.6 diff --git a/go.sum b/go.sum index 62b35539..fdc8ed9c 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DelineaXPM/tss-sdk-go/v3 v3.0.1 h1:4JBJukbaTjv2gJogF3MxZkrt7i+ayRhM//FgdJTKJ3Q= +github.com/DelineaXPM/tss-sdk-go/v3 v3.0.1/go.mod h1:VmyoHQ25FhSVHTI3/ptQNOviNEMfCy2ALAf/3E4Eqxg= github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 h1:s7/zwMi5w+KnlumDVbX1+P6mNAk5o7Wvx0VmvrQ7Bm0= github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4/go.mod h1:ipnA9Lpn5YM+FDSQZ7VWNjcuVurchInoGKm+v7O0sGs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= diff --git a/pkg/providers/secretserver/secretserver.go b/pkg/providers/secretserver/secretserver.go index 40e47c36..576a6b75 100644 --- a/pkg/providers/secretserver/secretserver.go +++ b/pkg/providers/secretserver/secretserver.go @@ -2,126 +2,85 @@ package secretserver import ( "crypto/tls" - "encoding/json" - "errors" "fmt" - "net/http" "os" + "strconv" "strings" + tssSdk "github.com/DelineaXPM/tss-sdk-go/v3/server" + "github.com/helmfile/vals/pkg/api" ) -type secretServerSecret struct { - Items []secretServerSecretItem `json:"items"` -} - -type secretServerSecretItem struct { - Slug string `json:"slug"` - ItemValue string `json:"itemValue"` -} - type provider struct { - APIVersion string - SSLVerify bool + tss tssSdk.Server } -func New(cfg api.StaticConfig) *provider { - p := &provider{} - v := cfg.String("ssl_verify") - p.SSLVerify = v != "false" - - if a := cfg.String("api_version"); a == "" { - p.APIVersion = "v1" - } else { - p.APIVersion = a +func New(cfg api.StaticConfig) (*provider, error) { + tss, err := tssSdk.New(tssSdk.Configuration{ + Credentials: tssSdk.UserCredential{ + Domain: os.Getenv("TSS_DOMAIN"), + Username: os.Getenv("TSS_USERNAME"), + Password: os.Getenv("TSS_PASSWORD"), + Token: os.Getenv("TSS_TOKEN"), + }, + ServerURL: os.Getenv("TSS_SERVER_URL"), + TLD: os.Getenv("TSS_TLD"), + Tenant: os.Getenv("TSS_TENANT"), + TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.String("ssl_verify") == "false"}, + }) + if err != nil { + return nil, err } - - return p + return &provider{tss: *tss}, nil } func (p *provider) GetString(key string) (string, error) { splits := strings.Split(key, "/") if len(splits) != 2 { - return "", fmt.Errorf("malformed key") + return "", fmt.Errorf("malformed key '%s'", key) } secretID := splits[0] fieldName := splits[1] - g, err := p.getSecret(secretID) + secret, err := p.getSecret(secretID) if err != nil { return "", err } - for _, item := range g.Items { - if item.Slug == fieldName { - return item.ItemValue, nil - } + if field, ok := secret.Field(fieldName); ok { + return field, nil + } else { + return "", fmt.Errorf("cannot find field %s in secret %s", fieldName, secretID) } - - return "", fmt.Errorf("cannot find field %s in secret", fieldName) } func (p *provider) GetStringMap(key string) (map[string]interface{}, error) { - secretMap := map[string]interface{}{} - secret, err := p.getSecret(key) if err != nil { - return secretMap, err + return nil, err } - for _, item := range secret.Items { + secretMap := map[string]interface{}{} + for _, item := range secret.Fields { + secretMap[item.FieldName] = item.ItemValue secretMap[item.Slug] = item.ItemValue } return secretMap, nil } -func (p *provider) getSecret(secretID string) (secretServerSecret, error) { - var secret secretServerSecret - accessToken, ok := os.LookupEnv("SECRETSERVER_TOKEN") - if !ok { - return secret, errors.New("missing SECRETSERVER_TOKEN environment variable") - } - baseUrl, ok := os.LookupEnv("SECRETSERVER_URL") - if !ok { - return secret, errors.New("missing SECRETSERVER_URL environment variable") - } - - url := fmt.Sprintf("%s/api/%s/secrets/%s", - baseUrl, - p.APIVersion, - secretID) - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: !p.SSLVerify}, - } - client := &http.Client{Transport: tr} - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return secret, err - } - req.Header = http.Header{ - "Content-Type": {"application/json"}, - "Authorization": {fmt.Sprintf("Bearer %s", accessToken)}, - } - - res, err := client.Do(req) - if err != nil { - return secret, err - } - - defer func() { - _ = res.Body.Close() - }() - - if res.StatusCode != http.StatusOK { - return secret, fmt.Errorf("SecretServer request %s failed: %s", req.URL, res.Status) - } - - err = json.NewDecoder(res.Body).Decode(&secret) - if err != nil { - return secret, fmt.Errorf("cannot decode JSON: %v", err) +func (p *provider) getSecret(key string) (*tssSdk.Secret, error) { + if i, err := strconv.Atoi(key); err == nil { + return p.tss.Secret(i) + } else { + secrets, err := p.tss.Secrets(key, "Name") + if err != nil { + return nil, err + } + if len(secrets) != 1 { + return nil, fmt.Errorf("expected exactly one secret with name '%s' but got %d", key, len(secrets)) + } + return &secrets[0], nil } - return secret, nil } diff --git a/pkg/stringprovider/stringprovider.go b/pkg/stringprovider/stringprovider.go index b3128d7a..5cc6815b 100644 --- a/pkg/stringprovider/stringprovider.go +++ b/pkg/stringprovider/stringprovider.go @@ -87,8 +87,8 @@ func New(l *log.Logger, provider api.StaticConfig, awsLogLevel string) (api.Lazy return httpjson.New(l, provider), nil case "scaleway": return scaleway.New(l, provider), nil - case "secretserver": - return secretserver.New(provider), nil + case "tss": + return secretserver.New(provider) case "infisical": return infisical.New(l, provider), nil } diff --git a/vals.go b/vals.go index eccabe20..09e57faa 100644 --- a/vals.go +++ b/vals.go @@ -114,7 +114,7 @@ const ( ProviderBitwarden = "bw" ProviderLockbox = "yclockbox" ProviderScaleway = "scw" - ProviderSecretserver = "secretserver" + ProviderSecretserver = "tss" ProviderInfisical = "infisical" ProviderServercore = "servercore" ) @@ -306,8 +306,7 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { p := scaleway.New(r.logger, conf) return p, nil case ProviderSecretserver: - p := secretserver.New(conf) - return p, nil + return secretserver.New(conf) case ProviderInfisical: p := infisical.New(r.logger, conf) return p, nil