Skip to content

Commit 4b2deeb

Browse files
Add certificates subcommand and change file path field name
1 parent 001262f commit 4b2deeb

File tree

2 files changed

+254
-23
lines changed

2 files changed

+254
-23
lines changed

certificate-agent-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
certificate-management: v1
1+
version: v1
22

33
infisical:
44
address: "https://app.infisical.com/"
@@ -44,7 +44,7 @@ certificates:
4444
command: "logger 'Certificate failed for api.mycompany.com'"
4545
timeout: 10
4646

47-
output-file-configuration:
47+
file-output:
4848
private-key-path: "./certs/web-server/private.key"
4949
certificate-path: "./certs/web-server/certificate.crt"
5050
certificate-chain-path: "./certs/web-server/chain.crt"

packages/cmd/agent.go

Lines changed: 252 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ type RetryConfig struct {
8989
}
9090

9191
type Config struct {
92-
CertificateManagement string `yaml:"certificate-management,omitempty"`
93-
Infisical InfisicalConfig `yaml:"infisical"`
94-
Auth AuthConfig `yaml:"auth"`
95-
Sinks []Sink `yaml:"sinks"`
96-
Cache CacheConfig `yaml:"cache,omitempty"`
97-
Templates []Template `yaml:"templates"`
98-
Certificates []AgentCertificateConfig `yaml:"certificates,omitempty"`
92+
Version string `yaml:"version,omitempty"`
93+
Infisical InfisicalConfig `yaml:"infisical"`
94+
Auth AuthConfig `yaml:"auth"`
95+
Sinks []Sink `yaml:"sinks"`
96+
Cache CacheConfig `yaml:"cache,omitempty"`
97+
Templates []Template `yaml:"templates"`
98+
Certificates []AgentCertificateConfig `yaml:"certificates,omitempty"`
9999
}
100100

101101
type TemplateWithID struct {
@@ -243,7 +243,7 @@ type AgentCertificateConfig struct {
243243
CertificateChainPath string `yaml:"certificate-chain-path,omitempty"`
244244
FilePermissions string `yaml:"file-permissions,omitempty"`
245245
DirectoryPermissions string `yaml:"directory-permissions,omitempty"`
246-
} `yaml:"output-file-configuration,omitempty"`
246+
} `yaml:"file-output,omitempty"`
247247
}
248248

249249
type DynamicSecretLeaseWithTTL struct {
@@ -777,35 +777,49 @@ func ParseAuthConfig(authConfigFile []byte, destination interface{}) error {
777777
}
778778

779779
func validateAgentConfigVersionCompatibility(config *Config) error {
780-
if config.CertificateManagement == "" {
780+
return validateAgentConfigVersionCompatibilityWithMode(config, false)
781+
}
782+
783+
func validateAgentConfigVersionCompatibilityWithMode(config *Config, isCertManagerMode bool) error {
784+
if config.Version == "" {
781785
if len(config.Certificates) > 0 {
782-
return fmt.Errorf("certificates are configured but 'certificate-management' version is not specified.")
786+
return fmt.Errorf("certificates are configured but 'version' field is not specified. Add 'version: v1' to your config")
783787
}
784788
return nil
785789
}
786790

787-
switch config.CertificateManagement {
791+
switch config.Version {
788792
case "v1":
789-
return validateCertificateManagementV1(config)
793+
if isCertManagerMode {
794+
return validateCertificateManagementV1ForCertManager(config)
795+
} else {
796+
return validateCertificateManagementV1(config)
797+
}
790798
default:
791-
return fmt.Errorf("unsupported certificate-management version: %s. Supported versions: v1", config.CertificateManagement)
799+
return fmt.Errorf("unsupported version: %s. Supported versions: v1", config.Version)
792800
}
793801
}
794802

795803
func validateCertificateManagementV1(config *Config) error {
796-
if len(config.Templates) > 0 {
797-
return fmt.Errorf("certificate-management: v1 does not support 'templates' configuration. Templates are for secret management, not certificate management. Please remove the templates section or use a different configuration mode")
798-
}
804+
return fmt.Errorf("version: v1 is for certificate management. Please use 'infisical cert-manager agent' for certificate configurations")
805+
}
799806

807+
func validateCertificateManagementV1ForCertManager(config *Config) error {
800808
if len(config.Certificates) == 0 {
801-
return fmt.Errorf("certificate-management: v1 requires at least one certificate to be configured in the 'certificates' section")
809+
return fmt.Errorf("certificate management requires at least one certificate to be configured")
802810
}
803-
804-
log.Info().Msg("Configuration validated for certificate-management: v1")
805811
return nil
806812
}
807813

808814
func ParseAgentConfig(configFile []byte) (*Config, error) {
815+
return parseAgentConfigWithMode(configFile, false)
816+
}
817+
818+
func ParseAgentConfigForCertManager(configFile []byte) (*Config, error) {
819+
return parseAgentConfigWithMode(configFile, true)
820+
}
821+
822+
func parseAgentConfigWithMode(configFile []byte, isCertManagerMode bool) (*Config, error) {
809823
var rawConfig Config
810824

811825
if err := yaml.Unmarshal(configFile, &rawConfig); err != nil {
@@ -827,7 +841,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
827841

828842
log.Info().Msgf("Infisical instance address set to %s", rawConfig.Infisical.Address)
829843

830-
if err := validateAgentConfigVersionCompatibility(&rawConfig); err != nil {
844+
if err := validateAgentConfigVersionCompatibilityWithMode(&rawConfig, isCertManagerMode); err != nil {
831845
return nil, err
832846
}
833847

@@ -3022,7 +3036,7 @@ var agentCmd = &cobra.Command{
30223036
}
30233037

30243038
var certificates []AgentCertificateConfig
3025-
if agentConfig.CertificateManagement != "" {
3039+
if agentConfig.Version != "" {
30263040
certificates = agentConfig.Certificates
30273041
}
30283042

@@ -3176,11 +3190,228 @@ var agentCmd = &cobra.Command{
31763190
},
31773191
}
31783192

3193+
func validateCertificateOnlyMode(config *Config) error {
3194+
if config.Version != "v1" {
3195+
return fmt.Errorf("certificate management requires version: v1")
3196+
}
3197+
3198+
if len(config.Certificates) == 0 {
3199+
return fmt.Errorf("certificate management requires at least one certificate to be configured")
3200+
}
3201+
3202+
if len(config.Templates) > 0 {
3203+
return fmt.Errorf("certificate-only mode does not support templates. Use regular 'infisical agent' for secrets management")
3204+
}
3205+
3206+
return nil
3207+
}
3208+
3209+
var certManagerCmd = &cobra.Command{
3210+
Use: "cert-manager",
3211+
Short: "Certificate management commands",
3212+
Long: "Commands for managing certificates through the Infisical agent",
3213+
}
3214+
3215+
var certManagerAgentCmd = &cobra.Command{
3216+
Example: `
3217+
infisical cert-manager agent --config certificate-agent-config.yaml
3218+
`,
3219+
Use: "agent",
3220+
Short: "Launch certificate management agent",
3221+
Long: "Used to launch a client daemon specifically for certificate management and lifecycle automation",
3222+
DisableFlagsInUseLine: true,
3223+
Run: func(cmd *cobra.Command, args []string) {
3224+
3225+
log.Info().Msg("starting Infisical certificate management agent...")
3226+
3227+
configPath, err := cmd.Flags().GetString("config")
3228+
if err != nil {
3229+
util.HandleError(err, "Unable to parse flag config")
3230+
}
3231+
3232+
var agentConfigInBytes []byte
3233+
3234+
agentConfigInBase64 := os.Getenv("INFISICAL_AGENT_CONFIG_BASE64")
3235+
3236+
if agentConfigInBase64 == "" {
3237+
data, err := ioutil.ReadFile(configPath)
3238+
if err != nil {
3239+
if !FileExists(configPath) {
3240+
log.Error().Msgf("Unable to locate %s. The provided agent config file path is either missing or incorrect", configPath)
3241+
return
3242+
}
3243+
}
3244+
agentConfigInBytes = data
3245+
}
3246+
3247+
if agentConfigInBase64 != "" {
3248+
decodedAgentConfig, err := base64.StdEncoding.DecodeString(agentConfigInBase64)
3249+
if err != nil {
3250+
log.Error().Msgf("Unable to decode base64 config file because %v", err)
3251+
return
3252+
}
3253+
3254+
agentConfigInBytes = decodedAgentConfig
3255+
}
3256+
3257+
if !FileExists(configPath) && agentConfigInBase64 == "" {
3258+
log.Error().Msgf("No agent config file provided at %v. Please provide a agent config file", configPath)
3259+
return
3260+
}
3261+
3262+
agentConfig, err := ParseAgentConfigForCertManager(agentConfigInBytes)
3263+
if err != nil {
3264+
log.Error().Msgf("Unable to parse %s because %v. Please ensure that it follows the Infisical Agent config structure", configPath, err)
3265+
return
3266+
}
3267+
3268+
if err := validateCertificateOnlyMode(agentConfig); err != nil {
3269+
log.Error().Msgf("Certificate-only mode validation failed: %v", err)
3270+
return
3271+
}
3272+
3273+
err = processCertificateCSRPaths(&agentConfig.Certificates)
3274+
if err != nil {
3275+
log.Error().Msgf("Failed to load CSR files: %v", err)
3276+
return
3277+
}
3278+
3279+
err = validateCertificateLifecycleConfig(&agentConfig.Certificates)
3280+
if err != nil {
3281+
log.Error().Msgf("Certificate lifecycle configuration validation failed: %v", err)
3282+
return
3283+
}
3284+
3285+
authMethodValid, authStrategy := util.IsAuthMethodValid(agentConfig.Auth.Type, false)
3286+
3287+
if !authMethodValid {
3288+
util.PrintErrorMessageAndExit(fmt.Sprintf("The auth method '%s' is not supported.", agentConfig.Auth.Type))
3289+
}
3290+
3291+
ctx, cancel := context.WithCancel(context.Background())
3292+
3293+
tokenRefreshNotifier := make(chan bool)
3294+
sigChan := make(chan os.Signal, 1)
3295+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
3296+
3297+
filePaths := agentConfig.Sinks
3298+
3299+
configBytes, err := yaml.Marshal(agentConfig.Auth.Config)
3300+
if err != nil {
3301+
log.Error().Msgf("unable to marshal auth config because %v", err)
3302+
cancel()
3303+
return
3304+
}
3305+
3306+
tm := NewAgentManager(NewAgentMangerOptions{
3307+
FileDeposits: filePaths,
3308+
Templates: []Template{}, // No templates in cert-only mode
3309+
Certificates: agentConfig.Certificates,
3310+
AuthConfigBytes: configBytes,
3311+
NewAccessTokenNotificationChan: tokenRefreshNotifier,
3312+
ExitAfterAuth: agentConfig.Infisical.ExitAfterAuth,
3313+
AuthStrategy: authStrategy,
3314+
RevokeCredentialsOnShutdown: agentConfig.Infisical.RevokeCredentialsOnShutdown,
3315+
RetryConfig: agentConfig.Infisical.RetryConfig,
3316+
})
3317+
3318+
tm.cacheManager, err = NewCacheManager(ctx, &agentConfig.Cache)
3319+
if err != nil {
3320+
log.Error().Msgf("unable to setup cache manager: %v", err)
3321+
cancel()
3322+
return
3323+
}
3324+
tm.dynamicSecretLeases = NewDynamicSecretLeaseManager(tm.cacheManager, tm.SdkRetryConfig())
3325+
3326+
go tm.ManageTokenLifecycle()
3327+
3328+
if len(agentConfig.Certificates) > 0 {
3329+
go func() {
3330+
for {
3331+
if tm.getTokenUnsafe() != "" {
3332+
break
3333+
}
3334+
time.Sleep(100 * time.Millisecond)
3335+
}
3336+
3337+
httpClient, err := tm.createAuthenticatedClient()
3338+
if err != nil {
3339+
log.Error().Msgf("failed to create authenticated client for name resolution: %v", err)
3340+
return
3341+
}
3342+
3343+
err = resolveCertificateNameReferences(&agentConfig.Certificates, httpClient)
3344+
if err != nil {
3345+
log.Error().Msgf("failed to resolve certificate name references: %v", err)
3346+
return
3347+
}
3348+
3349+
for i := range tm.certificates {
3350+
for j := range agentConfig.Certificates {
3351+
if tm.certificates[i].ID == j+1 {
3352+
tm.certificates[i].Certificate = agentConfig.Certificates[j]
3353+
break
3354+
}
3355+
}
3356+
}
3357+
}()
3358+
}
3359+
3360+
if len(tm.certificates) > 0 {
3361+
log.Info().Msg("certificate management engine starting...")
3362+
go tm.MonitorCertificates(ctx)
3363+
}
3364+
3365+
for {
3366+
select {
3367+
case <-tokenRefreshNotifier:
3368+
go tm.WriteTokenToFiles()
3369+
case <-sigChan:
3370+
tm.isShuttingDown = true
3371+
tm.cancelContext()
3372+
log.Info().Msg("certificate management agent is gracefully shutting down...")
3373+
cancel()
3374+
3375+
exitCode := 0
3376+
3377+
if !tm.exitAfterAuth && tm.revokeCredentialsOnShutdown {
3378+
3379+
done := make(chan error, 1)
3380+
3381+
go func() {
3382+
done <- tm.RevokeCredentials()
3383+
}()
3384+
3385+
select {
3386+
case err := <-done:
3387+
if err != nil {
3388+
log.Error().Msgf("unable to revoke credentials [err=%v]", err)
3389+
exitCode = 1
3390+
}
3391+
case <-time.After(5 * time.Minute):
3392+
log.Warn().Msg("credential revocation timed out after 5 minutes, forcing exit")
3393+
exitCode = 1
3394+
}
3395+
3396+
}
3397+
3398+
os.Exit(exitCode)
3399+
}
3400+
}
3401+
3402+
},
3403+
}
3404+
31793405
func init() {
31803406
agentCmd.SetHelpFunc(func(command *cobra.Command, strings []string) {
31813407
command.Flags().MarkHidden("domain")
31823408
command.Parent().HelpFunc()(command, strings)
31833409
})
31843410
agentCmd.Flags().String("config", "agent-config.yaml", "The path to agent config yaml file")
3411+
3412+
certManagerAgentCmd.Flags().String("config", "certificate-agent-config.yaml", "The path to certificate agent config yaml file")
3413+
certManagerCmd.AddCommand(certManagerAgentCmd)
3414+
31853415
rootCmd.AddCommand(agentCmd)
3416+
rootCmd.AddCommand(certManagerCmd)
31863417
}

0 commit comments

Comments
 (0)