Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Create separate key manager instances for certs, tokens, and db #382

Merged
merged 1 commit into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions cmd/apiserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,14 @@ func realMain(ctx context.Context) error {
}
defer db.Close()

// Setup signer
signer, err := keys.KeyManagerFor(ctx, &config.Database.Keys)
// Setup signers
tokenSigner, err := keys.KeyManagerFor(ctx, &config.TokenSigning.Keys)
if err != nil {
return fmt.Errorf("failed to crate key manager: %w", err)
return fmt.Errorf("failed to create token key manager: %w", err)
}
certificateSigner, err := keys.KeyManagerFor(ctx, &config.CertificateSigning.Keys)
if err != nil {
return fmt.Errorf("failed to create certificate key manager: %w", err)
}

// Create the router
Expand Down Expand Up @@ -149,13 +153,13 @@ func realMain(ctx context.Context) error {
// POST /api/verify
verifyChaff := chaff.New()
defer verifyChaff.Close()
verifyapiController := verifyapi.New(ctx, config, db, h, signer)
verifyapiController := verifyapi.New(ctx, config, db, h, tokenSigner)
r.Handle("/api/verify", handleChaff(verifyChaff, verifyapiController.HandleVerify())).Methods("POST")

// POST /api/certificate
certChaff := chaff.New()
defer certChaff.Close()
certapiController, err := certapi.New(ctx, config, db, h, signer)
certapiController, err := certapi.New(ctx, config, db, h, certificateSigner)
if err != nil {
return fmt.Errorf("failed to create certapi controller: %w", err)
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/config/api_server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ type APIServerConfig struct {
// Verification Token Config
// Currently this does not easily support rotation. TODO(mikehelmick) - add support.
VerificationTokenDuration time.Duration `env:"VERIFICATION_TOKEN_DURATION,default=24h"`
TokenSigningKey string `env:"TOKEN_SIGNING_KEY,required"`
TokenSigningKeyID string `env:"TOKEN_SIGNING_KEY_ID,default=v1"`
TokenIssuer string `env:"TOKEN_ISSUER,default=diagnosis-verification-example"`

// Verification certificate config
VerificationSettings CertificateSigningConfig
// Token signing
TokenSigning TokenSigningConfig

// Certificate signing
CertificateSigning CertificateSigningConfig

// Rate limiting configuration
RateLimit ratelimit.Config
Expand Down
14 changes: 11 additions & 3 deletions pkg/config/certificate_signing_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@

package config

import "time"
import (
"time"

// CertificateSigningConfig represents the settinsg for system wide certificate signing.
// these should be used if you are managing certifiate keys externally.
"github.com/google/exposure-notifications-server/pkg/keys"
)

// CertificateSigningConfig represents the settings for system-wide certificate
// signing. These should be used if you are managing certifiate keys externally.
type CertificateSigningConfig struct {
// Keys determines the key manager configuration for this certificate signing
// configuration.
Keys keys.Config `env:",prefix=CERTIFICATE_"`

PublicKeyCacheDuration time.Duration `env:"PUBLIC_KEY_CACHE_DURATION, default=15m"`
SignerCacheDuration time.Duration `env:"CERTIFICATE_SIGNER_CACHE_DURATION, default=1m"`
CertificateSigningKey string `env:"CERTIFICATE_SIGNING_KEY, required"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type ServerConfig struct {
AssetsPath string `env:"ASSETS_PATH,default=./cmd/server/assets"`

// Certificate signing key settings, needed for public key / settings display.
VerificationSettings CertificateSigningConfig
CertificateSigning CertificateSigningConfig

// If Dev mode is true, cookies aren't required to be sent over secure channels.
// This includes CSRF protection base cookie. You want this false in production (the default).
Expand Down
31 changes: 31 additions & 0 deletions pkg/config/token_signing_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
"github.com/google/exposure-notifications-server/pkg/keys"
)

// TokenSigningConfig represents the settings for system-wide certificate
// signing. These should be used if you are managing certifiate keys externally.
type TokenSigningConfig struct {
// Keys determines the key manager configuration for this token signing
// configuration.
Keys keys.Config `env:",prefix=TOKEN_"`

TokenSigningKey string `env:"TOKEN_SIGNING_KEY, required"`
TokenSigningKeyID string `env:"TOKEN_SIGNING_KEY_ID, default=v1"`
TokenIssuer string `env:"TOKEN_ISSUER, default=diagnosis-verification-example"`
}
8 changes: 4 additions & 4 deletions pkg/controller/certapi/certapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ type Controller struct {
func New(ctx context.Context, config *config.APIServerConfig, db *database.Database, h *render.Renderer, kms keys.KeyManager) (*Controller, error) {
logger := logging.FromContext(ctx)

pubKeyCache, err := keyutils.NewPublicKeyCache(config.VerificationSettings.PublicKeyCacheDuration)
pubKeyCache, err := keyutils.NewPublicKeyCache(config.CertificateSigning.PublicKeyCacheDuration)
if err != nil {
return nil, fmt.Errorf("cannot create public key cache, likely invalid duration: %w", err)
}

signerCache, err := cache.New(config.VerificationSettings.SignerCacheDuration)
signerCache, err := cache.New(config.CertificateSigning.SignerCacheDuration)
if err != nil {
return nil, fmt.Errorf("cannot create signer cache, likely invalid duration: %w", err)
}
Expand All @@ -78,7 +78,7 @@ func (c *Controller) validateToken(verToken string, publicKey crypto.PublicKey)
if !ok {
return nil, fmt.Errorf("missing 'kid' header in token")
}
if kid == c.config.TokenSigningKeyID {
if kid == c.config.TokenSigning.TokenSigningKeyID {
return publicKey, nil
}
return nil, fmt.Errorf("no public key for specified 'kid' not found: %v", kid)
Expand All @@ -96,7 +96,7 @@ func (c *Controller) validateToken(verToken string, publicKey crypto.PublicKey)
c.logger.Errorf("JWT is invalid: %v", err)
return "", nil, fmt.Errorf("verification token expired")
}
if !tokenClaims.VerifyIssuer(c.config.TokenIssuer, true) || !tokenClaims.VerifyAudience(c.config.TokenIssuer, true) {
if !tokenClaims.VerifyIssuer(c.config.TokenSigning.TokenIssuer, true) || !tokenClaims.VerifyAudience(c.config.TokenSigning.TokenIssuer, true) {
c.logger.Errorf("jwt contains invalid iss/aud: iss %v aud: %v", tokenClaims.Issuer, tokenClaims.Audience)
return "", nil, fmt.Errorf("verification token not valid")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/certapi/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (c *Controller) HandleCertificate() http.Handler {
}

// Get the public key for the token.
publicKey, err := c.pubKeyCache.GetPublicKey(ctx, c.config.TokenSigningKey, c.kms)
publicKey, err := c.pubKeyCache.GetPublicKey(ctx, c.config.TokenSigning.TokenSigningKey, c.kms)
if err != nil {
c.logger.Errorw("failed to get public key", "error", err)
c.h.RenderJSON(w, http.StatusInternalServerError, api.InternalError())
Expand Down
10 changes: 5 additions & 5 deletions pkg/controller/certapi/signers.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ func (c *Controller) getSignerForRealm(ctx context.Context, authApp *database.Au

if !realm.UseRealmCertificateKey {
// This realm is using the system key.
signer, err := c.kms.NewSigner(ctx, c.config.VerificationSettings.CertificateSigningKey)
signer, err := c.kms.NewSigner(ctx, c.config.CertificateSigning.CertificateSigningKey)
if err != nil {
return nil, fmt.Errorf("unable to get signing key from key manager: realmId: %v: %w", sRealmID, err)
}
return &SignerInfo{
Signer: signer,
KeyID: c.config.VerificationSettings.CertificateSigningKeyID,
Issuer: c.config.VerificationSettings.CertificateIssuer,
Audience: c.config.VerificationSettings.CertificateAudience,
Duration: c.config.VerificationSettings.CertificateDuration,
KeyID: c.config.CertificateSigning.CertificateSigningKeyID,
Issuer: c.config.CertificateSigning.CertificateIssuer,
Audience: c.config.CertificateSigning.CertificateAudience,
Duration: c.config.CertificateSigning.CertificateDuration,
}, nil
}

Expand Down
10 changes: 5 additions & 5 deletions pkg/controller/realmkeys/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ func (c *Controller) renderShow(ctx context.Context, w http.ResponseWriter, r *h

if !realm.UseRealmCertificateKey {
// load the system information.
m["certIssuer"] = c.config.VerificationSettings.CertificateIssuer
m["certAudience"] = c.config.VerificationSettings.CertificateAudience
m["certDuration"] = c.config.VerificationSettings.CertificateDuration
m["certKeyID"] = c.config.VerificationSettings.CertificateSigningKeyID
m["certIssuer"] = c.config.CertificateSigning.CertificateIssuer
m["certAudience"] = c.config.CertificateSigning.CertificateAudience
m["certDuration"] = c.config.CertificateSigning.CertificateDuration
m["certKeyID"] = c.config.CertificateSigning.CertificateSigningKeyID
// Download and PEM encode the public key.
publicKey, err := c.publicKeyCache.GetPublicKey(ctx, c.config.VerificationSettings.CertificateSigningKey, c.db.KeyManager())
publicKey, err := c.publicKeyCache.GetPublicKey(ctx, c.config.CertificateSigning.CertificateSigningKey, c.db.KeyManager())
if err != nil {
m["certPublicKeyError"] = fmt.Sprintf("Error loading public key: %v", err)
} else {
Expand Down
8 changes: 4 additions & 4 deletions pkg/controller/verifyapi/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (c *Controller) HandleVerify() http.Handler {
}

// Get the signer based on Key configuration.
signer, err := c.kms.NewSigner(ctx, c.config.TokenSigningKey)
signer, err := c.kms.NewSigner(ctx, c.config.TokenSigning.TokenSigningKey)
if err != nil {
c.logger.Errorw("failed to get signer", "error", err)
c.h.RenderJSON(w, http.StatusInternalServerError, api.InternalError())
Expand Down Expand Up @@ -118,15 +118,15 @@ func (c *Controller) HandleVerify() http.Handler {
subject := verificationToken.Subject()
now := time.Now().UTC()
claims := &jwt.StandardClaims{
Audience: c.config.TokenIssuer,
Audience: c.config.TokenSigning.TokenIssuer,
ExpiresAt: now.Add(c.config.VerificationTokenDuration).Unix(),
Id: verificationToken.TokenID,
IssuedAt: now.Unix(),
Issuer: c.config.TokenIssuer,
Issuer: c.config.TokenSigning.TokenIssuer,
Subject: subject.String(),
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
token.Header[verifyapi.KeyIDHeader] = c.config.TokenSigningKeyID
token.Header[verifyapi.KeyIDHeader] = c.config.TokenSigning.TokenSigningKeyID
signedJWT, err := jwthelper.SignJWT(token, signer)
if err != nil {
c.logger.Errorw("failed to sign token", "error", err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/database/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type Config struct {

// Keys is the key management configuration. This is used to resolve values
// that are encrypted via a KMS.
Keys keys.Config
Keys keys.Config `env:",prefix=DB_"`

// The KMS managed KeyRing that per-realm certificate signing keys are
// created on.
Expand Down
6 changes: 5 additions & 1 deletion terraform/services.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ locals {
}

database_config = {
DB_KEY_MANAGER = "GOOGLE_CLOUD_KMS"
DB_APIKEY_DATABASE_KEY = "secret://${google_secret_manager_secret_version.db-apikey-db-hmac.id}"
DB_APIKEY_SIGNATURE_KEY = "secret://${google_secret_manager_secret_version.db-apikey-sig-hmac.id}"
DB_ENCRYPTION_KEY = google_kms_crypto_key.database-encrypter.self_link
Expand Down Expand Up @@ -67,9 +68,12 @@ locals {
}

signing_config = {
CERTIFICATE_KEY_MANAGER = "GOOGLE_CLOUD_KMS"
CERTIFICATE_SIGNING_KEY = trimprefix(data.google_kms_crypto_key_version.certificate-signer-version.id, "//cloudkms.googleapis.com/v1/")
TOKEN_SIGNING_KEY = trimprefix(data.google_kms_crypto_key_version.token-signer-version.id, "//cloudkms.googleapis.com/v1/")
CERTIFICATE_SIGNING_KEYRING = google_kms_key_ring.verification.self_link

TOKEN_KEY_MANAGER = "GOOGLE_CLOUD_KMS"
TOKEN_SIGNING_KEY = trimprefix(data.google_kms_crypto_key_version.token-signer-version.id, "//cloudkms.googleapis.com/v1/")
}
}

Expand Down