Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
125 changes: 42 additions & 83 deletions pkg/providers/secretserver/secretserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and a blank line.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)

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)
}
Comment on lines +51 to 55
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else branch is unnecessary after a return statement. The code can be simplified by removing the else and unindenting the error return, which improves readability and follows Go best practices.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback


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
}
4 changes: 2 additions & 2 deletions pkg/stringprovider/stringprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pd-gov why change secretserver to tss? this is a beaking change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to align the naming scheme with that used by the vendor. I though this was Okay, since the Provider was introduced just a few days ago.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yxxhero I can change it back if you prefere

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pd-gov maybe we can add alias. like : case "tss" "secretserver"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pd-gov Or add some notes?

Copy link
Contributor Author

@pd-gov pd-gov Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yxxhero What do you mean by adding notes? In the readme?

My assumption was, that no one was actually using the secretserver provider yet, since I added it just before Christmas. To bad there already is a release containing the longer name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pd-gov you are right. so we can use new name. I will add the changes in the release notes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yxxhero The current version contains your proposed aliases. Should I keep that in for backwards compatibility?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pd-gov Using new name is ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yxxhero Okay, I removed the alias again.

return secretserver.New(provider), nil
case "tss":
return secretserver.New(provider)
case "infisical":
return infisical.New(l, provider), nil
}
Expand Down
5 changes: 2 additions & 3 deletions vals.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const (
ProviderBitwarden = "bw"
ProviderLockbox = "yclockbox"
ProviderScaleway = "scw"
ProviderSecretserver = "secretserver"
ProviderSecretserver = "tss"
ProviderInfisical = "infisical"
ProviderServercore = "servercore"
)
Expand Down Expand Up @@ -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
Expand Down