From 45ba6843eae2897ab6db6c845762a7d766a2d883 Mon Sep 17 00:00:00 2001 From: Mike Mason Date: Tue, 7 May 2024 14:13:51 -0500 Subject: [PATCH] support client credentials being loaded from file (#48) The client creds secret, previously had to be specified in the config. When deploying this with the helm chart, this would require the value to be stored in plain text in the config map. To solve this, we now accept a file path for both clientID and clientSecret. If either attributes value is prefixed with `file://` the defined file is attempted to be loaded. If no file is found, or the file fails to be read, an error is returned. Currently the file is only read once at startup. However a future improvement could provide support for allowing hot reloading of the file. Signed-off-by: Mike Mason --- chart/iam-runtime-infratographer/README.md | 4 +-- chart/iam-runtime-infratographer/values.yaml | 4 +++ internal/accesstoken/tokensource.go | 33 ++++++++++++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/chart/iam-runtime-infratographer/README.md b/chart/iam-runtime-infratographer/README.md index 031ad5c7..70b84faf 100644 --- a/chart/iam-runtime-infratographer/README.md +++ b/chart/iam-runtime-infratographer/README.md @@ -58,8 +58,8 @@ iam-runtime-infratographer: | config.accessToken.exchange.grantType | string | urn:ietf:params:oauth:grant-type:token-exchange | grantType configures the grant type | | config.accessToken.exchange.issuer | string | `""` | issuer specifies the URL for the issuer for the exchanged token. The Issuer must support OpenID discovery to discover the token endpoint. | | config.accessToken.exchange.tokenType | string | urn:ietf:params:oauth:token-type:jwt | tokenType configures the token type | -| config.accessToken.source.clientCredentials.clientID | string | `""` | clientID is the client credentials id which is used to retrieve a token from the issuer. | -| config.accessToken.source.clientCredentials.clientSecret | string | `""` | clientSecret is the client credentials secret which is used to retrieve a token from the issuer. | +| config.accessToken.source.clientCredentials.clientID | string | `""` | clientID is the client credentials id which is used to retrieve a token from the issuer. This attribute also supports a file path by prefixing the value with `file://`. example: `file:///var/secrets/client-id` | +| config.accessToken.source.clientCredentials.clientSecret | string | `""` | clientSecret is the client credentials secret which is used to retrieve a token from the issuer. This attribute also supports a file path by prefixing the value with `file://`. example: `file:///var/secrets/client-secret` | | config.accessToken.source.clientCredentials.issuer | string | `""` | issuer specifies the URL for the issuer for the token request. The Issuer must support OpenID discovery to discover the token endpoint. | | config.accessToken.source.fileToken.noReuseToken | bool | `false` | noReuseToken if enabled disables reuse of tokens while they're still valid. | | config.accessToken.source.fileToken.tokenPath | string | `""` | tokenPath is the path to the source jwt token. | diff --git a/chart/iam-runtime-infratographer/values.yaml b/chart/iam-runtime-infratographer/values.yaml index 05972855..7c1c9d4a 100644 --- a/chart/iam-runtime-infratographer/values.yaml +++ b/chart/iam-runtime-infratographer/values.yaml @@ -51,8 +51,12 @@ config: # The Issuer must support OpenID discovery to discover the token endpoint. issuer: "" # -- clientID is the client credentials id which is used to retrieve a token from the issuer. + # This attribute also supports a file path by prefixing the value with `file://`. + # example: `file:///var/secrets/client-id` clientID: "" # -- clientSecret is the client credentials secret which is used to retrieve a token from the issuer. + # This attribute also supports a file path by prefixing the value with `file://`. + # example: `file:///var/secrets/client-secret` clientSecret: "" exchange: # -- issuer specifies the URL for the issuer for the exchanged token. diff --git a/internal/accesstoken/tokensource.go b/internal/accesstoken/tokensource.go index c799a57a..c1d9d4d3 100644 --- a/internal/accesstoken/tokensource.go +++ b/internal/accesstoken/tokensource.go @@ -3,6 +3,10 @@ package accesstoken import ( "context" "fmt" + "net/url" + "os" + "path/filepath" + "strings" "go.infratographer.com/iam-runtime-infratographer/internal/jwt" "golang.org/x/oauth2" @@ -57,9 +61,34 @@ func (c ClientCredentialConfig) toTokenSource(ctx context.Context) (oauth2.Token return nil, fmt.Errorf("failed to fetch issuer token endpoint: %w", err) } + clientID := c.ClientID + clientSecret := c.ClientSecret + + if uri, err := url.ParseRequestURI(clientID); err == nil && uri.Scheme == "file" { + file := filepath.Join(uri.Host, uri.Path) + + content, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("failed to read file %s: %w", file, err) + } + + clientID = strings.TrimSpace(string(content)) + } + + if uri, err := url.ParseRequestURI(clientSecret); err == nil && uri.Scheme == "file" { + file := filepath.Join(uri.Host, uri.Path) + + content, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("failed to read file %s: %w", file, err) + } + + clientSecret = strings.TrimSpace(string(content)) + } + config := clientcredentials.Config{ - ClientID: c.ClientID, - ClientSecret: c.ClientSecret, + ClientID: clientID, + ClientSecret: clientSecret, TokenURL: tokenEndpoint, }