From bbb7bddf7f85de64465cf14745463eeff32b3f32 Mon Sep 17 00:00:00 2001 From: y-myajima <137997379+y-myajima@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:56:36 +0900 Subject: [PATCH] Feat/token naming format (#163) * add token naming format Signed-off-by: myajima * fix comment Signed-off-by: myajima --------- Signed-off-by: myajima --- Makefile | 6 ++++++ athenz-sia.env | 15 +++++++++++++++ pkg/config/config.go | 8 ++++++++ pkg/config/default.go | 12 +++++++----- pkg/config/derived-token-file.go | 33 ++++++++++++++++++++++++++------ pkg/config/model.go | 8 ++++++-- pkg/token/service.go | 20 ++++++++++++------- 7 files changed, 82 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index eb9c793f..c599fb90 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,12 @@ endif ifneq ($(ATHENZ_SIA_DEFAULT_INTERMEDIATE_CERT_BUNDLE),) LDFLAGS_ARGS += -X 'github.com/AthenZ/k8s-athenz-sia/v3/pkg/config.DEFAULT_INTERMEDIATE_CERT_BUNDLE=$(ATHENZ_SIA_DEFAULT_INTERMEDIATE_CERT_BUNDLE)' endif +ifneq ($(ATHENZ_SIA_DEFAULT_ACCESS_TOKEN_FILENAME_DELIMITER),) +LDFLAGS_ARGS += -X 'github.com/AthenZ/k8s-athenz-sia/v3/pkg/config.DEFAULT_ACCESS_TOKEN_FILENAME_DELIMITER=$(ATHENZ_SIA_DEFAULT_ACCESS_TOKEN_FILENAME_DELIMITER)' +endif +ifneq ($(ATHENZ_SIA_DEFAULT_ROLE_TOKEN_FILENAME_DELIMITER),) +LDFLAGS_ARGS += -X 'github.com/AthenZ/k8s-athenz-sia/v3/pkg/config.DEFAULT_ROLE_TOKEN_FILENAME_DELIMITER=$(ATHENZ_SIA_DEFAULT_ROLE_TOKEN_FILENAME_DELIMITER)' +endif ifneq ($(LDFLAGS_ARGS),) LDFLAGS += -ldflags "$(LDFLAGS_ARGS) -linkmode=external" diff --git a/athenz-sia.env b/athenz-sia.env index 416d63bf..4c1f44b5 100644 --- a/athenz-sia.env +++ b/athenz-sia.env @@ -215,6 +215,21 @@ TOKEN_SERVER_TLS_KEY_PATH= # TOKEN_DIR= # +# Naming format for the file that outputs the AccessToken(e.g. /var/run/athenz/accesstokens/{{domain}}{{delimiter}}{{role}}.accesstoken) +# +ACCESS_TOKEN_NAMING_FORMAT= +# +# File name delimiter for Athenz AccessToken files +# +ACCESS_TOKEN_FILENAME_DELIMITER=:role. +# +# Naming format for the file that outputs the RoleToken(e.g. /var/run/athenz/roletokens/{{domain}}{{delimiter}}{{role}}.roletoken) +# +ROLE_TOKEN_NAMING_FORMAT= +# +# File name delimiter for Athenz RoleToken files +ROLE_TOKEN_FILENAME_DELIMITER=:role. +# # Server address to listen as metrics exporter sidecar (e.g. :9999) # METRICS_SERVER_ADDR= diff --git a/pkg/config/config.go b/pkg/config/config.go index 9a794d4b..975ca5bb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -109,6 +109,10 @@ func (idCfg *IdentityConfig) loadFromENV() error { loadEnv("TOKEN_SERVER_TLS_CERT_PATH", &idCfg.tokenServerTLSCertPath) loadEnv("TOKEN_SERVER_TLS_KEY_PATH", &idCfg.tokenServerTLSKeyPath) loadEnv("TOKEN_DIR", &idCfg.tokenDir) + loadEnv("ACCESS_TOKEN_NAMING_FORMAT", &idCfg.accessTokenNamingFormat) + loadEnv("ACCESS_TOKEN_FILENAME_DELIMITER", &idCfg.accessTokenFilenameDelimiter) + loadEnv("ROLE_TOKEN_NAMING_FORMAT", &idCfg.roleTokenNamingFormat) + loadEnv("ROLE_TOKEN_FILENAME_DELIMITER", &idCfg.roleTokenFilenameDelimiter) loadEnv("METRICS_SERVER_ADDR", &idCfg.MetricsServerAddr) loadEnv("DELETE_INSTANCE_ID", &idCfg.rawDeleteInstanceID) loadEnv("USE_TOKEN_SERVER", &idCfg.rawUseTokenServer) @@ -218,6 +222,10 @@ func (idCfg *IdentityConfig) loadFromFlag(program string, args []string) error { f.StringVar(&idCfg.tokenServerTLSCertPath, "token-server-tls-cert-path", idCfg.tokenServerTLSCertPath, "token server TLS certificate path (if empty, disable TLS)") f.StringVar(&idCfg.tokenServerTLSKeyPath, "token-server-tls-key-path", idCfg.tokenServerTLSKeyPath, "token server TLS certificate key path (if empty, disable TLS)") f.StringVar(&idCfg.tokenDir, "token-dir", idCfg.tokenDir, "directory to write token files") + f.StringVar(&idCfg.accessTokenNamingFormat, "access-token-naming-format", idCfg.accessTokenNamingFormat, "The file name format when outputting the access token to a file") + f.StringVar(&idCfg.accessTokenFilenameDelimiter, "access-token-filename-delimiter", idCfg.accessTokenFilenameDelimiter, "The delimiter that separates the domain name and role name when outputting the access token to a file") + f.StringVar(&idCfg.roleTokenNamingFormat, "role-token-naming-format", idCfg.roleTokenNamingFormat, "The file name format when outputting the role token to a file") + f.StringVar(&idCfg.roleTokenFilenameDelimiter, "role-token-filename-delimiter", idCfg.roleTokenFilenameDelimiter, "The delimiter that separates the domain name and role name when outputting the role token to a file") f.StringVar(&idCfg.MetricsServerAddr, "metrics-server-addr", idCfg.MetricsServerAddr, "HTTP server address to provide metrics") f.BoolVar(&idCfg.DeleteInstanceID, "delete-instance-id", idCfg.DeleteInstanceID, "delete x509 certificate record from identity provider on shutdown (true/false)") // Token Server diff --git a/pkg/config/default.go b/pkg/config/default.go index 9273f5ca..51033241 100644 --- a/pkg/config/default.go +++ b/pkg/config/default.go @@ -37,11 +37,13 @@ var ( DEFAULT_ROLE_CERT_EXPIRY_TIME_BUFFER_MINUTES_RAW = "5" DEFAULT_ROLE_CERT_EXPIRY_TIME_BUFFER_MINUTES = 5 - DEFAULT_ENDPOINT string - DEFAULT_ROLE_AUTH_HEADER = "Athenz-Role-Auth" - DEFAULT_DNS_SUFFIX = "athenz.cloud" - DEFAULT_ROLE_CERT_FILENAME_DELIMITER = ":role." - DEFAULT_INTERMEDIATE_CERT_BUNDLE string + DEFAULT_ENDPOINT string + DEFAULT_ROLE_AUTH_HEADER = "Athenz-Role-Auth" + DEFAULT_DNS_SUFFIX = "athenz.cloud" + DEFAULT_ROLE_CERT_FILENAME_DELIMITER = ":role." + DEFAULT_ACCESS_TOKEN_FILENAME_DELIMITER = ":role." + DEFAULT_ROLE_TOKEN_FILENAME_DELIMITER = ":role." + DEFAULT_INTERMEDIATE_CERT_BUNDLE string // default values for graceful shutdown DEFAULT_SHUTDOWN_TIMEOUT = 5 * time.Second diff --git a/pkg/config/derived-token-file.go b/pkg/config/derived-token-file.go index fa069a4d..efb69278 100644 --- a/pkg/config/derived-token-file.go +++ b/pkg/config/derived-token-file.go @@ -16,6 +16,7 @@ package config import ( + "fmt" "path/filepath" "strings" ) @@ -27,7 +28,6 @@ type TokenFileConfig struct { } type DerivedTokenFile struct { - Dir string // TODO: This might be deleted later AccessToken TokenFileConfig RoleToken TokenFileConfig } @@ -36,7 +36,6 @@ type DerivedTokenFile struct { func (idCfg *IdentityConfig) derivedTokenFileConfig() error { // default: idCfg.TokenFile = DerivedTokenFile{ - Dir: "", AccessToken: TokenFileConfig{ Use: false, Format: "", @@ -51,13 +50,21 @@ func (idCfg *IdentityConfig) derivedTokenFileConfig() error { // TODO: Apply the following instead?: // if idCfg.TokenDir == "" || idCfg.TokenType == "" { - if idCfg.tokenDir == "" { + if idCfg.tokenDir == "" && idCfg.accessTokenNamingFormat == "" && idCfg.roleTokenNamingFormat == "" { return nil // disabled } + // If both the TokenDir settings and the NamingFormat settings are configured redundantly, an error will be returned. + if idCfg.tokenDir != "" && idCfg.accessTokenNamingFormat != "" { + return fmt.Errorf("Both TOKEN_DIR[%s] and ACCESS_TOKEN_NAMING_FORMAT[%s] are set. Please ensure only one of these is specified to avoid conflicts.", idCfg.tokenDir, idCfg.accessTokenNamingFormat) + } + if idCfg.tokenDir != "" && idCfg.roleTokenNamingFormat != "" { + return fmt.Errorf("Both TOKEN_DIR[%s] and ROLE_TOKEN_NAMING_FORMAT[%s] are set. Please ensure only one of these is specified to avoid conflicts.", idCfg.tokenDir, idCfg.roleTokenNamingFormat) + + } + // Enable from now on: idCfg.TokenFile = DerivedTokenFile{ - Dir: idCfg.tokenDir, AccessToken: func() TokenFileConfig { // disabled if !strings.Contains(idCfg.TokenType, "accesstoken") { @@ -67,10 +74,17 @@ func (idCfg *IdentityConfig) derivedTokenFileConfig() error { Delimiter: "", } } + if idCfg.accessTokenNamingFormat != "" { + return TokenFileConfig{ + Use: true, + Format: idCfg.accessTokenNamingFormat, + Delimiter: idCfg.accessTokenFilenameDelimiter, + } + } return TokenFileConfig{ Use: true, Format: filepath.Join(idCfg.tokenDir, "{{domain}}{{delimiter}}{{role}}.accesstoken"), - Delimiter: ":role.", + Delimiter: idCfg.accessTokenFilenameDelimiter, } }(), @@ -83,10 +97,17 @@ func (idCfg *IdentityConfig) derivedTokenFileConfig() error { Delimiter: "", } } + if idCfg.roleTokenNamingFormat != "" { + return TokenFileConfig{ + Use: true, + Format: idCfg.roleTokenNamingFormat, + Delimiter: idCfg.roleTokenFilenameDelimiter, + } + } return TokenFileConfig{ Use: true, Format: filepath.Join(idCfg.tokenDir, "{{domain}}{{delimiter}}{{role}}.roletoken"), - Delimiter: ":role.", + Delimiter: idCfg.roleTokenFilenameDelimiter, } }(), } diff --git a/pkg/config/model.go b/pkg/config/model.go index a134549b..435fd2fb 100644 --- a/pkg/config/model.go +++ b/pkg/config/model.go @@ -60,8 +60,12 @@ type IdentityConfig struct { roleCertKeyNamingFormat string // // Token Cache Derived State and its related fields: - TokenFile DerivedTokenFile - tokenDir string + TokenFile DerivedTokenFile + tokenDir string + accessTokenFilenameDelimiter string + accessTokenNamingFormat string + roleTokenFilenameDelimiter string + roleTokenNamingFormat string // // Token Server Derived State and its related fields: TokenServer DerivedTokenServer diff --git a/pkg/token/service.go b/pkg/token/service.go index 048d30ee..37a691ce 100644 --- a/pkg/token/service.go +++ b/pkg/token/service.go @@ -64,12 +64,14 @@ func New(ctx context.Context, idCfg *config.IdentityConfig) (daemon.Daemon, erro log.Info("Skipped token provider initiation") return nil, nil } - // TODO: In the next PR, the determination will be made on a per Access Token and Role Token basis. // TODO: move to derived token file - // TODO: Maybe if !idCfg.TokenFile.Use() - if !idCfg.TokenFile.AccessToken.Use && !idCfg.TokenFile.RoleToken.Use { + if !idCfg.TokenFile.AccessToken.Use { // When file output is disabled, the Dir settings for the access token and role token will all be empty strings. - log.Debugf("Skipping to write token files to directory with empty TOKEN_DIR [%s]", idCfg.TokenFile.Dir) + log.Debugf("Skipping to write access token files to directory with empty filename format[%s]", idCfg.TokenFile.AccessToken.Format) + } + if !idCfg.TokenFile.RoleToken.Use { + // When file output is disabled, the Dir settings for the access token and role token will all be empty strings. + log.Debugf("Skipping to write role token files to directory with empty filename format[%s]", idCfg.TokenFile.RoleToken.Format) } // initialize token cache with placeholder @@ -401,6 +403,9 @@ func (d *tokenService) updateAndWriteFileToken(key CacheKey, tt mode) error { // File output processing domain, role := key.Domain, key.Role token := d.accessTokenCache.Load(key) + if token == nil { + return fmt.Errorf("failed to load access token from cache: %s", key.String()) + } outPath, err := extutil.GeneratePath(d.idCfg.TokenFile.AccessToken.Format, domain, role, d.idCfg.TokenFile.AccessToken.Delimiter) if err != nil { return fmt.Errorf("failed to generate path for access token with format [%s], domain [%s], role [%s], delimiter [%s]: %w", d.idCfg.TokenFile.AccessToken.Format, domain, role, d.idCfg.TokenFile.AccessToken.Delimiter, err) @@ -415,6 +420,9 @@ func (d *tokenService) updateAndWriteFileToken(key CacheKey, tt mode) error { // File output processing domain, role := key.Domain, key.Role token := d.roleTokenCache.Load(key) + if token == nil { + return fmt.Errorf("failed to load role token from cache: %s", key.String()) + } outPath, err := extutil.GeneratePath(d.idCfg.TokenFile.RoleToken.Format, domain, role, d.idCfg.TokenFile.RoleToken.Delimiter) if err != nil { return fmt.Errorf("failed to generate path for role token with format [%s], domain [%s], role [%s], delimiter [%s]: %w", d.idCfg.TokenFile.RoleToken.Format, domain, role, d.idCfg.TokenFile.RoleToken.Delimiter, err) @@ -445,8 +453,6 @@ func (d *tokenService) writeFile(token Token, outPath string, tt mode) error { return fmt.Errorf("invalid token type: %d", tt) } - domain := token.Domain() - role := token.Role() rawToken := token.Raw() if rawToken == "" { // skip placeholder token added during daemon creation @@ -458,7 +464,7 @@ func (d *tokenService) writeFile(token Token, outPath string, tt mode) error { return fmt.Errorf("unable to create directory for token: %w", err) } // Unlike the delimiter used for file names, the log output will use the Athenz standard delimiter ":role.": - log.Infof("[New %s Token] Subject: %s:role.%s [%d bytes] in %s", tokenType, domain, role, len(rawToken), outPath) + log.Infof("[New %s Token] Subject: %s:role.%s [%d bytes] in %s", tokenType, token.Domain(), token.Role(), len(rawToken), outPath) if err := w.AddBytes(outPath, 0644, []byte(rawToken)); err != nil { return fmt.Errorf("unable to save %s Token: %w", tokenType, err) }