Skip to content
Merged
Prev Previous commit
Next Next commit
chore: Rename AppSigningKey to AppSecurityKey
  • Loading branch information
Emyrk committed Apr 5, 2023
commit fe28e42220513eb5007fca5b861d2427a0c5845f
14 changes: 7 additions & 7 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
// Read the app signing key from the DB. We store it hex encoded
// since the config table uses strings for the value and we
// don't want to deal with automatic encoding issues.
appSigningKeyStr, err := tx.GetAppSigningKey(ctx)
appSecurityKeyStr, err := tx.GetAppSecurityKey(ctx)
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
return xerrors.Errorf("get app signing key: %w", err)
}
Expand All @@ -794,26 +794,26 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
// generated automatically on failure. Any workspace app token
// smuggling operations in progress may fail, although with a
// helpful error.
if decoded, err := hex.DecodeString(appSigningKeyStr); err != nil || len(decoded) != len(workspaceapps.SigningKey{}) {
b := make([]byte, len(workspaceapps.SigningKey{}))
if decoded, err := hex.DecodeString(appSecurityKeyStr); err != nil || len(decoded) != len(workspaceapps.SecurityKey{}) {
b := make([]byte, len(workspaceapps.SecurityKey{}))
_, err := rand.Read(b)
if err != nil {
return xerrors.Errorf("generate fresh app signing key: %w", err)
}

appSigningKeyStr = hex.EncodeToString(b)
err = tx.UpsertAppSigningKey(ctx, appSigningKeyStr)
appSecurityKeyStr = hex.EncodeToString(b)
err = tx.UpsertAppSecurityKey(ctx, appSecurityKeyStr)
if err != nil {
return xerrors.Errorf("insert freshly generated app signing key to database: %w", err)
}
}

appSigningKey, err := workspaceapps.KeyFromString(appSigningKeyStr)
appSecurityKey, err := workspaceapps.KeyFromString(appSecurityKeyStr)
if err != nil {
return xerrors.Errorf("decode app signing key from database: %w", err)
}

options.AppSigningKey = appSigningKey
options.AppSecurityKey = appSecurityKey
return nil
}, nil)
if err != nil {
Expand Down
10 changes: 5 additions & 5 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ type Options struct {
SwaggerEndpoint bool
SetUserGroups func(ctx context.Context, tx database.Store, userID uuid.UUID, groupNames []string) error
TemplateScheduleStore schedule.TemplateScheduleStore
// AppSigningKey denotes the symmetric key to use for signing temporary app
// tokens.
AppSigningKey workspaceapps.SigningKey
// AppSecurityKey is the crypto key used to sign and encrypt tokens related to
// workspace applications. It consists of both a signing and encryption key.
AppSecurityKey workspaceapps.SecurityKey
HealthcheckFunc func(ctx context.Context) (*healthcheck.Report, error)
HealthcheckTimeout time.Duration
HealthcheckRefresh time.Duration
Expand Down Expand Up @@ -302,7 +302,7 @@ func New(options *Options) *API {
options.DeploymentValues,
oauthConfigs,
options.AgentInactiveDisconnectTimeout,
options.AppSigningKey,
options.AppSecurityKey,
),
metricsCache: metricsCache,
Auditor: atomic.Pointer[audit.Auditor]{},
Expand Down Expand Up @@ -340,7 +340,7 @@ func New(options *Options) *API {

SignedTokenProvider: api.WorkspaceAppsProvider,
WorkspaceConnCache: api.workspaceAgentCache,
AppSigningKey: options.AppSigningKey,
AppSecurityKey: options.AppSecurityKey,
}

apiKeyMiddleware := httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
Expand Down
6 changes: 3 additions & 3 deletions coderd/coderdtest/coderdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ import (
"github.com/coder/coder/testutil"
)

// AppSigningKey is a 64-byte key used to sign JWTs and encrypt JWEs for
// AppSecurityKey is a 96-byte key used to sign JWTs and encrypt JWEs for
// workspace app tokens in tests.
var AppSigningKey = must(workspaceapps.KeyFromString("6465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e2077617320686572"))
var AppSecurityKey = must(workspaceapps.KeyFromString("6465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e207761732068657265206465616e2077617320686572"))

type Options struct {
// AccessURL denotes a custom access URL. By default we use the httptest
Expand Down Expand Up @@ -338,7 +338,7 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can
DeploymentValues: options.DeploymentValues,
UpdateCheckOptions: options.UpdateCheckOptions,
SwaggerEndpoint: options.SwaggerEndpoint,
AppSigningKey: AppSigningKey,
AppSecurityKey: AppSecurityKey,
SSHConfig: options.ConfigSSH,
HealthcheckFunc: options.HealthcheckFunc,
HealthcheckTimeout: options.HealthcheckTimeout,
Expand Down
8 changes: 4 additions & 4 deletions coderd/database/dbauthz/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,14 +379,14 @@ func (q *querier) GetLogoURL(ctx context.Context) (string, error) {
return q.db.GetLogoURL(ctx)
}

func (q *querier) GetAppSigningKey(ctx context.Context) (string, error) {
func (q *querier) GetAppSecurityKey(ctx context.Context) (string, error) {
// No authz checks
return q.db.GetAppSigningKey(ctx)
return q.db.GetAppSecurityKey(ctx)
}

func (q *querier) UpsertAppSigningKey(ctx context.Context, data string) error {
func (q *querier) UpsertAppSecurityKey(ctx context.Context, data string) error {
// No authz checks as this is done during startup
return q.db.UpsertAppSigningKey(ctx, data)
return q.db.UpsertAppSecurityKey(ctx, data)
}

func (q *querier) GetServiceBanner(ctx context.Context) (string, error) {
Expand Down
10 changes: 5 additions & 5 deletions coderd/database/dbfake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ type data struct {
lastUpdateCheck []byte
serviceBanner []byte
logoURL string
appSigningKey string
appSecurityKey string
lastLicenseID int32
}

Expand Down Expand Up @@ -4444,18 +4444,18 @@ func (q *fakeQuerier) GetLogoURL(_ context.Context) (string, error) {
return q.logoURL, nil
}

func (q *fakeQuerier) GetAppSigningKey(_ context.Context) (string, error) {
func (q *fakeQuerier) GetAppSecurityKey(_ context.Context) (string, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()

return q.appSigningKey, nil
return q.appSecurityKey, nil
}

func (q *fakeQuerier) UpsertAppSigningKey(_ context.Context, data string) error {
func (q *fakeQuerier) UpsertAppSecurityKey(_ context.Context, data string) error {
q.mutex.Lock()
defer q.mutex.Unlock()

q.appSigningKey = data
q.appSecurityKey = data
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coderd/database/queries/siteconfig.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'logo_url';
-- name: GetLogoURL :one
SELECT value FROM site_configs WHERE key = 'logo_url';

-- name: GetAppSigningKey :one
-- name: GetAppSecurityKey :one
SELECT value FROM site_configs WHERE key = 'app_signing_key';

-- name: UpsertAppSigningKey :exec
-- name: UpsertAppSecurityKey :exec
INSERT INTO site_configs (key, value) VALUES ('app_signing_key', $1)
ON CONFLICT (key) DO UPDATE set value = $1 WHERE site_configs.key = 'app_signing_key';
2 changes: 1 addition & 1 deletion coderd/workspaceapps.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (api *API) workspaceApplicationAuth(rw http.ResponseWriter, r *http.Request
}

// Encrypt the API key.
encryptedAPIKey, err := api.AppSigningKey.EncryptAPIKey(workspaceapps.EncryptedAPIKeyPayload{
encryptedAPIKey, err := api.AppSecurityKey.EncryptAPIKey(workspaceapps.EncryptedAPIKeyPayload{
APIKey: cookie.Value,
})
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions coderd/workspaceapps/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ type DBTokenProvider struct {
DeploymentValues *codersdk.DeploymentValues
OAuth2Configs *httpmw.OAuth2Configs
WorkspaceAgentInactiveTimeout time.Duration
SigningKey SigningKey
SigningKey SecurityKey
}

var _ SignedTokenProvider = &DBTokenProvider{}

func NewDBTokenProvider(log slog.Logger, accessURL *url.URL, authz rbac.Authorizer, db database.Store, cfg *codersdk.DeploymentValues, oauth2Cfgs *httpmw.OAuth2Configs, workspaceAgentInactiveTimeout time.Duration, signingKey SigningKey) SignedTokenProvider {
func NewDBTokenProvider(log slog.Logger, accessURL *url.URL, authz rbac.Authorizer, db database.Store, cfg *codersdk.DeploymentValues, oauth2Cfgs *httpmw.OAuth2Configs, workspaceAgentInactiveTimeout time.Duration, signingKey SecurityKey) SignedTokenProvider {
if workspaceAgentInactiveTimeout == 0 {
workspaceAgentInactiveTimeout = 1 * time.Minute
}
Expand Down
6 changes: 3 additions & 3 deletions coderd/workspaceapps/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func Test_ResolveRequest(t *testing.T) {
require.Equal(t, codersdk.DevURLSignedAppTokenCookie, cookie.Name)
require.Equal(t, req.BasePath, cookie.Path)

parsedToken, err := api.AppSigningKey.VerifySignedToken(cookie.Value)
parsedToken, err := api.AppSecurityKey.VerifySignedToken(cookie.Value)
require.NoError(t, err)
// normalize expiry
require.WithinDuration(t, token.Expiry, parsedToken.Expiry, 2*time.Second)
Expand Down Expand Up @@ -482,7 +482,7 @@ func Test_ResolveRequest(t *testing.T) {
AgentID: agentID,
AppURL: appURL,
}
badTokenStr, err := api.AppSigningKey.SignToken(badToken)
badTokenStr, err := api.AppSecurityKey.SignToken(badToken)
require.NoError(t, err)

req := workspaceapps.Request{
Expand Down Expand Up @@ -518,7 +518,7 @@ func Test_ResolveRequest(t *testing.T) {
require.Len(t, cookies, 1)
require.Equal(t, cookies[0].Name, codersdk.DevURLSignedAppTokenCookie)
require.NotEqual(t, cookies[0].Value, badTokenStr)
parsedToken, err := api.AppSigningKey.VerifySignedToken(cookies[0].Value)
parsedToken, err := api.AppSecurityKey.VerifySignedToken(cookies[0].Value)
require.NoError(t, err)
require.Equal(t, appNameOwner, parsedToken.AppSlugOrPort)
})
Expand Down
6 changes: 3 additions & 3 deletions coderd/workspaceapps/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ type Server struct {
RealIPConfig *httpmw.RealIPConfig

SignedTokenProvider SignedTokenProvider
WorkspaceConnCache *wsconncache.Cache
AppSigningKey SigningKey
WorkspaceConnCache *wsconncache.Cache
AppSecurityKey SecurityKey

websocketWaitMutex sync.Mutex
websocketWaitGroup sync.WaitGroup
Expand Down Expand Up @@ -245,7 +245,7 @@ func (s *Server) SubdomainAppMW(middlewares ...func(http.Handler) http.Handler)
// cookie and strip that query parameter.
if encryptedAPIKey := r.URL.Query().Get(SubdomainProxyAPIKeyParam); encryptedAPIKey != "" {
// Exchange the encoded API key for a real one.
token, err := s.AppSigningKey.DecryptAPIKey(encryptedAPIKey)
token, err := s.AppSecurityKey.DecryptAPIKey(encryptedAPIKey)
if err != nil {
s.Logger.Debug(ctx, "could not decrypt API key", slog.Error(err))
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
Expand Down
34 changes: 22 additions & 12 deletions coderd/workspaceapps/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,24 @@ func (t SignedToken) MatchesRequest(req Request) bool {
t.AppSlugOrPort == req.AppSlugOrPort
}

// AppSigningKey is used for signing and encrypting app tokens and API keys.
// SecurityKey is used for signing and encrypting app tokens and API keys.
//
// The first 64 bytes of the key are used for signing tokens with HMAC-SHA256,
// and the last 32 bytes are used for encrypting API keys with AES-256-GCM.
type SigningKey [96]byte
// We use a single key for both operations to avoid having to store and manage
// two keys.
type SecurityKey [96]byte

func KeyFromString(str string) (SigningKey, error) {
var key SigningKey
func (k SecurityKey) signingKey() []byte {
return k[:64]
}

func (k SecurityKey) encryptionKey() []byte {
return k[64:]
}

func KeyFromString(str string) (SecurityKey, error) {
var key SecurityKey
decoded, err := hex.DecodeString(str)
if err != nil {
return key, xerrors.Errorf("decode key: %w", err)
Expand All @@ -67,7 +77,7 @@ func KeyFromString(str string) (SigningKey, error) {
// SignToken generates a signed workspace app token with the given payload. If
// the payload doesn't have an expiry, it will be set to the current time plus
// the default expiry.
func (k SigningKey) SignToken(payload SignedToken) (string, error) {
func (k SecurityKey) SignToken(payload SignedToken) (string, error) {
if payload.Expiry.IsZero() {
payload.Expiry = time.Now().Add(DefaultTokenExpiry)
}
Expand All @@ -78,7 +88,7 @@ func (k SigningKey) SignToken(payload SignedToken) (string, error) {

signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: tokenSigningAlgorithm,
Key: k[:64],
Key: k.signingKey(),
}, nil)
if err != nil {
return "", xerrors.Errorf("create signer: %w", err)
Expand All @@ -100,7 +110,7 @@ func (k SigningKey) SignToken(payload SignedToken) (string, error) {
// VerifySignedToken parses a signed workspace app token with the given key and
// returns the payload. If the token is invalid or expired, an error is
// returned.
func (k SigningKey) VerifySignedToken(str string) (SignedToken, error) {
func (k SecurityKey) VerifySignedToken(str string) (SignedToken, error) {
object, err := jose.ParseSigned(str)
if err != nil {
return SignedToken{}, xerrors.Errorf("parse JWS: %w", err)
Expand All @@ -112,7 +122,7 @@ func (k SigningKey) VerifySignedToken(str string) (SignedToken, error) {
return SignedToken{}, xerrors.Errorf("expected token signing algorithm to be %q, got %q", tokenSigningAlgorithm, object.Signatures[0].Header.Algorithm)
}

output, err := object.Verify(k[:64])
output, err := object.Verify(k.signingKey())
if err != nil {
return SignedToken{}, xerrors.Errorf("verify JWS: %w", err)
}
Expand All @@ -135,7 +145,7 @@ type EncryptedAPIKeyPayload struct {
}

// EncryptAPIKey encrypts an API key for subdomain token smuggling.
func (k SigningKey) EncryptAPIKey(payload EncryptedAPIKeyPayload) (string, error) {
func (k SecurityKey) EncryptAPIKey(payload EncryptedAPIKeyPayload) (string, error) {
if payload.APIKey == "" {
return "", xerrors.New("API key is empty")
}
Expand All @@ -155,7 +165,7 @@ func (k SigningKey) EncryptAPIKey(payload EncryptedAPIKeyPayload) (string, error
jose.A256GCM,
jose.Recipient{
Algorithm: apiKeyEncryptionAlgorithm,
Key: k[64:],
Key: k.encryptionKey(),
},
&jose.EncrypterOptions{
Compression: jose.DEFLATE,
Expand All @@ -174,7 +184,7 @@ func (k SigningKey) EncryptAPIKey(payload EncryptedAPIKeyPayload) (string, error
}

// DecryptAPIKey undoes EncryptAPIKey and is used in the subdomain app handler.
func (k SigningKey) DecryptAPIKey(encryptedAPIKey string) (string, error) {
func (k SecurityKey) DecryptAPIKey(encryptedAPIKey string) (string, error) {
encrypted, err := base64.RawURLEncoding.DecodeString(encryptedAPIKey)
if err != nil {
return "", xerrors.Errorf("base64 decode encrypted API key: %w", err)
Expand All @@ -189,7 +199,7 @@ func (k SigningKey) DecryptAPIKey(encryptedAPIKey string) (string, error) {
}

// Decrypt using the hashed secret.
decrypted, err := object.Decrypt(k[64:])
decrypted, err := object.Decrypt(k.encryptionKey())
if err != nil {
return "", xerrors.Errorf("decrypt API key: %w", err)
}
Expand Down
Loading