Verwaltung von Config-Dateien in AWS mit AWS Secrets Manager

Verwaltung von Config-Dateien in AWS mit AWS Secrets Manager

1. März 2023

Einführung

image

Es gibt einige Probleme, die mir bisher nicht bewusst waren, aber ständig Unannehmlichkeiten verursacht haben. Alle Secrets, die auf dem Server verwendet werden, wurden in einer Datei namens config.yaml gespeichert.

“Kann man diese wirklich als Quelle in Github speichern?” fragte ich mich.

Github bietet die Möglichkeit, verschiedene Secrets zu verwalten, die in einem Repository verwendet werden, durch die Github Secrets Funktion. Trotzdem hatte ich das Gefühl, diese Informationen wären ungeschützt ausgesetzt.

Erste Überlegung: Github Secrets

  • Ich habe darüber nachgedacht, alle Variablen, die beim Build benötigt werden, durch die Secrets-Funktion von Github zu verbergen.
  • Diese Methode könnte zwar hilfreich sein, um bestimmte Variablen zu verbergen, allerdings ist es schwierig, später zu erkennen, wann diese Variablen injiziert werden. Zudem funktioniert es nur innerhalb der beschränkten Umgebung von Github, daher schien ein allgemeiner nutzbarer Ansatz notwendig.

Ziel

Während ich über verschiedene Ansätze nachdachte, legte ich folgende drei Kriterien fest, um ein Tool zur Verwaltung von Secrets zu wählen:

  1. Es sollte einfach zu bedienen und der Einstieg so unkompliziert sein, dass andere Teammitglieder es sofort nutzen können.
  2. Es sollte eine allgemeine Methode bieten, die in verschiedenen Services verwendet werden kann.
  3. Overengineering sollte vermieden werden, und es sollte minimalen Änderungen erfordern, um config zu lesen und zu nutzen.

ChatGPT

  • Mir fehlte das Wissen, also musste ich zunächst nach Alternativen suchen. Früher hätte ich Google zu Rate gezogen, aber ich nutzte das Explorationstool ChatGPT.

    image

  • Vault und Key-Management-Systeme, die von anderen Cloud-Diensten bereitgestellt werden, fielen mir auf. Da Acloset viele der von AWS angebotenen Dienste nutzt, beschlossen wir, einen Blick auf den AWS Secret Manager zu werfen, der gut zu AWS passen könnte.

    image

  • Ich erhielt einen Überblick über die Haupt- und Nebenfunktionen.

AWS Secrets Manager

  • Zögern bei der Entwicklung bringt nichts! Also habe ich es sofort ausprobiert.

    image

Schritte

  • Es war nicht nur möglich, einfache Schlüssel-Wert-Speicher zu erstellen, sondern auch AWS RDS-Informationen und andere Datenbankinformationen durch Eingabe von Adresse und Port zu konfigurieren.

    image

  • Da das Ziel darin besteht, die aktuell im Dienst verwendete config.yaml zu verschlüsseln, wählte ich eine andere Art von Sicherheitsschlüssel.

    image

  • Wenngleich man durch die von AWS erstellten Schlüssel-Wert-Paare auch Verschlüsselungen erzeugen kann, entschied ich mich dafür, den gesamten Text der config.yaml einzugeben und klickte dazu auf Plain Text.

    image

  • Obwohl der Standardwert als {} eingegeben ist und man vielleicht denken könnte, dass nur das Json-Format möglich ist, ist eigentlich jeder Text möglich. Ich habe den kompletten Inhalt unserer Server-config.yaml dort eingefügt.

    image
  • Anschließend folgt die Konfiguration des Sicherheitsschlüssels. Da der Name des Sicherheitsschlüssels der Schlüssel ist, der vom AWS-Client verwendet wird, um auf das Secret zuzugreifen, sollte dieser weise gewählt werden. Ich habe ihn als someapp/config/dev eingegeben.

    image
  • Der letzte Schritt: Zurzeit wird kein Rotation Setting vorgenommen, aber es scheint eine nützliche Funktion zu sein, die man in Zukunft in einer Rotation-Umgebung oder für Schlüssel, die regelmäßig rotiert werden müssen, nutzen kann. Ohne zusätzliche Einstellungen wird gespeichert.

    image

Secret Rotation

  • Während einige Secrets, wie DB-Informationen oder Passwörter, statisch sind, gibt es auch dynamische Secrets, z.B. API-Token von externen Diensten.
  • AWS Secrets Manager bietet eine komfortable Methode, um solche dynamischen Schlüssel regelmäßig zu aktualisieren und zu speichern, indem es AWS Lambda-Funktionen bereitstellt.

Server

  • Jetzt sind alle Configs im SecretManager, doch der AWS Access Key ID und AWS Secret Access Key werden auf dem Server verwaltet. Um darüber nachzudenken, wie sie zur Buildzeit injiziert werden können, entschied ich mich für Github Secrets.

Injizierungsszenario

  • Das grundlegende Geheimnisinjektionsszenario wurde wie folgt festgelegt:

    image

Schritte

Aktionen

Wir ändern die Argumente während des Builds in Github Actions.

      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
	  {% raw %}
          tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            ARG_ENVIRONMENT=${{ env.ENVIRONMENT }}
            ARG_AWS_REGION=${{ env.AWS_REGION }}
            ARG_AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}
            ARG_AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}
	  {% endraw %}

Anstatt den ursprünglichen CONFIG_FILE Pfad zu injizieren, erhielten wir die Informationen für den Zugriff auf AWS: Region, AWS Access Key ID, AWS Secret Access Key und Environment, die jenes kontrollieren, in dem das Build durchgeführt wird (z. B. dev).

Dockerfile

Im Dockerfile werden die Argumente empfangen, damit die entsprechenden Schlüssel über Flags in den Server gelangen können.

ARG ARG_ENVIRONMENT
ARG ARG_AWS_ACCESS_KEY_ID
ARG ARG_AWS_SECRET_ACCESS_KEY
ARG ARG_AWS_REGION

ENV ENVIRONMENT=$ARG_ENVIRONMENT
ENV AWS_ACCESS_KEY_ID=$ARG_AWS_ACCESS_KEY_ID
ENV AWS_SECRET_ACCESS_KEY=$ARG_AWS_SECRET_ACCESS_KEY
ENV AWS_REGION=$ARG_AWS_REGION

CMD ["sh", "-c", "/app/someapp \
    --aws-access-key-id $AWS_ACCESS_KEY_ID \
    --aws-secret-access-key $AWS_SECRET_ACCESS_KEY \
    --aws-region $AWS_REGION \
    --environment $ENVIRONMENT \

Server

Anstatt den Config-Pfad zu injizieren, haben wir es so verändert, dass AWS Access Key ID und Secret Access Key injiziert werden, und änderten die Flags, um jedes Environment zu injizieren.

func ParseFlags() *Flags {
	flags := &Flags{}
	flag.StringVar(&flags.AWSAccessKeyID, "aws-access-key-id", "", "aws access key id")
	flag.StringVar(&flags.AWSSecretAccessKey, "aws-secret-access-key", "", "aws secret access key")
	flag.StringVar(&flags.AWSRegion, "aws-region", "", "aws region")
	flag.StringVar(&flags.Environment, "environment", "", "environment")
	flag.Parse()
	return flags
}

Die Reihenfolge der Dependency Injection wurde so geändert, dass AWS zuerst erstellt werden kann.

	app := fx.New(
		fx.Provide(
			cli.ParseFlags,

			aws.NewAwsSession, // this!
			aws.NewAwsSecretsManager, // this!

			config.New,
			echoRouter.New,
      // ... (gekürzt)

In config.New() wird secretManager und flags injiziert, um secretString zu erhalten, und nach der Unmarshal Operation in YAML wird Config zurückgegeben.

func New(flags *cli.Flags, secretsManager *secretsmanager.SecretsManager) *Config {
	var (
		config    Config // injizierte Config Struktur
		configKey = fmt.Sprintf(SecretManagerConfigKey, flags.Environment) // "someapp/config/dev"
	)

	result, err := secretsManager.GetSecretValue(&secretsmanager.GetSecretValueInput{
		SecretId:     aws.String(configKey),
		VersionStage: aws.String(versionStage),
	})
	if err != nil {
		log.New().Fatalf("err can not get config from secretsManager key: %v", configKey)
	}

	if err = yaml.Unmarshal([]byte(*result.SecretString), &config); err != nil {
		log.New().Fatalf("err unmarshal yaml from secretString err: %v", err)
	}

	return &config
}

Ergebnis

Der Server wurde erfolgreich verbunden und läuft nun.

image