Verwaltung von Config-Dateien in AWS mit AWS Secrets Manager
Einführung
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:
- Es sollte einfach zu bedienen und der Einstieg so unkompliziert sein, dass andere Teammitglieder es sofort nutzen können.
- Es sollte eine allgemeine Methode bieten, die in verschiedenen Services verwendet werden kann.
- 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.
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.
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.
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.
Da das Ziel darin besteht, die aktuell im Dienst verwendete
config.yaml
zu verschlüsseln, wählte ich eine andere Art von Sicherheitsschlüssel.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.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.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.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.
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:
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.