Documentation ¶
Overview ¶
Package secretrotation helps with secret rotation when a service must accept old and new secrets for a time.
- SecretHolder is a DB service that provides the secret. For example a AWS Secret Manager service. This should provide 3 secrets (default: comma separated).
- Provider is for example a web server validating secret in incoming API calls. At any time, it always accepts any of the 3 secrets.
- Consumer is for example an application calling the web server. It is always sending the secret in the middle.
The reason for the 3 secrets is we always want a valid secret, given:
- In the SecretHolder, the secret rotation can happen anytime (but not too frequent).
- The providers can load the secrets anytime.
- The consumers can load the secrets anytime.
Assertions:
- The refresh rate on the Providers and Consumers is faster than the secret rotation frequency in the secret holder.
- We have 3 secrets: {PREVIOUS,CURRENT,PENDING}. This is AWS design but that seems robust.
See: https://docs.aws.amazon.com/secretsmanager/latest/userguide/getting-started.html
Alternative similar design:
- store only 2 secrets in the SecretHolder but with the change time, and share rotation duration details with the consumer. (A lot more complicated)
Example ¶
package main import ( "errors" "fmt" "github.com/vincentkerdraon/configo/secretrotation" ) func main() { m := secretrotation.New() //Init not done _, err := m.Current() if !errors.Is(err, secretrotation.MissingInitValuesError{}) { panic(err) } //Provide input rs := secretrotation.NewRotatingSecret("my_secretA", "my_secretB", "my_secretC") err = m.Set(rs) handleErr(err) //Check received secret is allowed (this is the producer point of view) if !m.Allowed("my_secretC") { panic("expected secret OK") } //Get secret to use (this is the consumer point of view) secret, err := m.Current() handleErr(err) fmt.Print(secret) } func handleErr(err error) { if err != nil { panic(err) } }
Output: my_secretB
Index ¶
Examples ¶
Constants ¶
View Source
const SecretRedacted = "[redacted]"
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type InvalidSecretError ¶ added in v0.2.0
type InvalidSecretError struct {
Err error
}
func (InvalidSecretError) Error ¶ added in v0.2.0
func (err InvalidSecretError) Error() string
func (InvalidSecretError) Unwrap ¶ added in v0.2.0
func (err InvalidSecretError) Unwrap() error
type Manager ¶
func (*Manager) AllowedNonConstant ¶
AllowedNonConstant checks if a given key match the secrets. This is NOT using the crypto security on timing attacks. This is faster than Allowed()
func (*Manager) Current ¶
Current returns the secret to use when the consumer is calling the provider.
func (*Manager) RotatingSecret ¶
func (m *Manager) RotatingSecret() (RotatingSecret, error)
func (*Manager) Set ¶
func (m *Manager) Set(rs RotatingSecret) error
type MissingInitValuesError ¶ added in v0.2.0
type MissingInitValuesError struct{}
func (MissingInitValuesError) Error ¶ added in v0.2.0
func (MissingInitValuesError) Error() string
type RotatingSecret ¶
func NewRotatingSecret ¶
func NewRotatingSecret(previous, current, pending Secret) RotatingSecret
func (*RotatingSecret) Deserialize ¶
func (rs *RotatingSecret) Deserialize(s string) error
func (*RotatingSecret) Range ¶
func (rs *RotatingSecret) Range(f func(Secret) (continueRange bool))
Range iterates over the secrets
func (*RotatingSecret) RedactSecret ¶
func (rs *RotatingSecret) RedactSecret(in string) string
Example ¶
package main import ( "fmt" "github.com/vincentkerdraon/configo/secretrotation" ) func main() { rs := secretrotation.NewRotatingSecret("my_secretA", "my_secretB", "my_secretC") const input = "my string including my_secretA" const expected = "my string including [redacted]" redacted := rs.RedactSecret(input) if redacted != expected { panic("expect ok") } fmt.Printf("redacted=%q\n", redacted) }
Output: redacted="my string including [redacted]"
func (RotatingSecret) Serialize ¶
func (rs RotatingSecret) Serialize() string
Example ¶
package main import ( "fmt" "github.com/vincentkerdraon/configo/secretrotation" ) func main() { rs := secretrotation.NewRotatingSecret("my_secretA", "my_secretB", "my_secretC") serial := rs.Serialize() //Serialize rs2 := secretrotation.RotatingSecret{} if err := rs2.Deserialize(serial); err != nil { panic("expect ok") } if rs2.Current != rs.Current { panic("expect ok") } fmt.Printf("serial=%q, rs2=%+v\n", serial, rs2) }
Output: serial="my_secretA,my_secretB,my_secretC", rs2={Previous:my_secretA Current:my_secretB Pending:my_secretC}
func (RotatingSecret) Validate ¶
func (rs RotatingSecret) Validate() error
Example ¶
package main import ( "fmt" "github.com/vincentkerdraon/configo/secretrotation" ) func main() { rs := secretrotation.NewRotatingSecret("my_secretA", "my_secretB", "my_secretC") if rs.Validate() != nil { panic("expect ok") } rs = secretrotation.NewRotatingSecret("my_secretA", "my_secretB", "") if rs.Validate() == nil { panic("expect fail") } fmt.Printf("Validate=%q\n", rs.Validate()) }
Output: Validate="empty secret"
Click to show internal directories.
Click to hide internal directories.