secretrotation

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2022 License: MIT Imports: 4 Imported by: 1

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:

  1. In the SecretHolder, the secret rotation can happen anytime (but not too frequent).
  2. The providers can load the secrets anytime.
  3. 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

type Manager struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

func New

func New() *Manager

func (*Manager) Allowed

func (m *Manager) Allowed(in Secret) bool

Allowed checks if a given key match the secrets.

func (*Manager) AllowedNonConstant

func (m *Manager) AllowedNonConstant(in Secret) bool

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

func (m *Manager) Current() (Secret, error)

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

type RotatingSecret

type RotatingSecret struct {
	Previous Secret
	Current  Secret
	Pending  Secret
}

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"

type Secret

type Secret string

func (Secret) RedactSecret

func (s Secret) RedactSecret(in string) string

func (*Secret) Set

func (s *Secret) Set(in string) error

func (Secret) String

func (s Secret) String() string

func (Secret) Validate

func (s Secret) Validate() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL