Address remaining compliance portal review nits
Fill in certificate renewal processing, preserve OAuth and JWKS edge cases, embed the compliance-portal app in production builds, and close the smaller portal routing and n8n update gaps. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -91,8 +91,38 @@ func (h *renewHandler) Claim(ctx context.Context) (coredata.Certificate, error)
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
func (h *renewHandler) Process(_ context.Context, _ coredata.Certificate) error {
|
||||
return nil
|
||||
func (h *renewHandler) Process(ctx context.Context, certificate coredata.Certificate) error {
|
||||
return h.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, tx pg.Tx) error {
|
||||
fullCertificate := &coredata.Certificate{}
|
||||
if err := fullCertificate.LoadByIDForUpdateSkipLocked(ctx, tx, coredata.NewNoScope(), certificate.ID); err != nil {
|
||||
return fmt.Errorf("cannot load certificate for renewal: %w", err)
|
||||
}
|
||||
|
||||
if fullCertificate.Status != coredata.CertificateStatusRenewing {
|
||||
return nil
|
||||
}
|
||||
|
||||
fullCertificate.HTTPChallengeToken = nil
|
||||
fullCertificate.HTTPChallengeKeyAuth = nil
|
||||
fullCertificate.HTTPChallengeURL = nil
|
||||
fullCertificate.HTTPOrderURL = nil
|
||||
fullCertificate.ProvisioningError = nil
|
||||
|
||||
if err := fullCertificate.Update(ctx, tx, coredata.NewNoScope()); err != nil {
|
||||
return fmt.Errorf("cannot prepare certificate for renewal: %w", err)
|
||||
}
|
||||
|
||||
h.logger.InfoCtx(
|
||||
ctx,
|
||||
"prepared certificate for renewal",
|
||||
log.String("hostname", fullCertificate.Hostname),
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (h *renewHandler) RecoverStale(ctx context.Context) error {
|
||||
|
||||
@@ -78,8 +78,6 @@ func PublicURLForTrustCenter(
|
||||
host = byID[*trustCenter.CustomDomainID].Domain
|
||||
case trustCenter.DefaultDomainID != nil && byID[*trustCenter.DefaultDomainID] != nil:
|
||||
host = byID[*trustCenter.DefaultDomainID].Domain
|
||||
case trustCenter.CustomDomainID != nil && byID[*trustCenter.CustomDomainID] != nil:
|
||||
host = byID[*trustCenter.CustomDomainID].Domain
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
|
||||
@@ -389,7 +389,10 @@ WITH combined AS (
|
||||
COALESCE(cf.organization_id, tc.organization_id) AS organization_id,
|
||||
COALESCE(cf.trust_center_id, tc.id) AS trust_center_id,
|
||||
f.id AS framework_id,
|
||||
ROW_NUMBER() OVER (ORDER BY f.created_at, f.id) AS rank,
|
||||
CASE
|
||||
WHEN cf.id IS NOT NULL THEN cf.rank
|
||||
ELSE ROW_NUMBER() OVER (ORDER BY f.created_at, f.id)
|
||||
END AS rank,
|
||||
CASE WHEN cf.id IS NULL THEN 'NONE' ELSE 'PUBLIC' END AS visibility,
|
||||
COALESCE(cf.created_at, f.created_at) AS created_at,
|
||||
COALESCE(cf.updated_at, f.updated_at) AS updated_at
|
||||
|
||||
@@ -121,14 +121,23 @@ func RSAPublicKeyFromJWK(jwk JWK) (*rsa.PublicKey, error) {
|
||||
return nil, fmt.Errorf("cannot decode rsa exponent: %w", err)
|
||||
}
|
||||
|
||||
e := new(big.Int).SetBytes(eBytes)
|
||||
if !e.IsInt64() || e.Sign() <= 0 {
|
||||
return nil, fmt.Errorf("cannot convert jwk to rsa public key: invalid rsa exponent")
|
||||
}
|
||||
|
||||
return &rsa.PublicKey{
|
||||
N: new(big.Int).SetBytes(nBytes),
|
||||
E: int(new(big.Int).SetBytes(eBytes).Int64()),
|
||||
E: int(e.Int64()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PublicKeyFromJWKS returns the RSA public key matching the given key ID.
|
||||
func PublicKeyFromJWKS(jwks *JWKS, kid string) (*rsa.PublicKey, error) {
|
||||
if jwks == nil {
|
||||
return nil, fmt.Errorf("cannot find signing key %q in jwks: jwks is nil", kid)
|
||||
}
|
||||
|
||||
for _, key := range jwks.Keys {
|
||||
if key.KeyID == kid {
|
||||
return RSAPublicKeyFromJWK(key)
|
||||
@@ -181,6 +190,10 @@ func VerifyJWT(raw string, pubKey *rsa.PublicKey) ([]byte, error) {
|
||||
|
||||
// VerifyJWTWithJWKS verifies an RS256 JWT using the matching key from a JWKS.
|
||||
func VerifyJWTWithJWKS(raw string, jwks *JWKS) ([]byte, error) {
|
||||
if jwks == nil {
|
||||
return nil, fmt.Errorf("cannot verify jwt: jwks is nil")
|
||||
}
|
||||
|
||||
parts := strings.Split(raw, ".")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("cannot verify jwt: invalid format")
|
||||
|
||||
@@ -325,6 +325,21 @@ func TestRSAPublicKeyFromJWK(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects invalid rsa exponent",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
invalidExponent := jwk
|
||||
invalidExponent.E = base64.RawURLEncoding.EncodeToString(
|
||||
new(big.Int).Lsh(big.NewInt(1), 128).Bytes(),
|
||||
)
|
||||
|
||||
_, err := jose.RSAPublicKeyFromJWK(invalidExponent)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestPublicKeyFromJWKS(t *testing.T) {
|
||||
@@ -358,6 +373,16 @@ func TestPublicKeyFromJWKS(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"errors when jwks is nil",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := jose.PublicKeyFromJWKS(nil, "kid-1")
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestVerifyJWT(t *testing.T) {
|
||||
@@ -559,4 +584,17 @@ func TestVerifyJWTWithJWKS(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"rejects nil jwks",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
token, err := jose.SignJWT(key, "kid-1", map[string]string{"sub": "test"})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = jose.VerifyJWTWithJWKS(token, nil)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,14 @@ func AuthorizationURLWithQuery(
|
||||
return "", fmt.Errorf("cannot parse authorization endpoint: %w", err)
|
||||
}
|
||||
|
||||
u.RawQuery = query.Encode()
|
||||
merged := u.Query()
|
||||
for key, values := range query {
|
||||
for _, value := range values {
|
||||
merged.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
u.RawQuery = merged.Encode()
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
@@ -90,6 +90,26 @@ func TestAuthorizationURLWithQuery(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestAuthorizationURLWithQueryPreservesEndpointQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
authorizationEndpoint := uri.URI(
|
||||
"https://auth.example.com/api/connect/v1/oauth2/authorize?tenant=acme",
|
||||
)
|
||||
query := url.Values{}
|
||||
query.Set("client_id", "https://trust.example.com/.well-known/oauth-client-metadata")
|
||||
query.Set("response_type", "code")
|
||||
|
||||
got, err := oauth2.AuthorizationURLWithQuery(authorizationEndpoint, query)
|
||||
require.NoError(t, err)
|
||||
|
||||
parsed, err := url.Parse(got)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "acme", parsed.Query().Get("tenant"))
|
||||
assert.Equal(t, "https://trust.example.com/.well-known/oauth-client-metadata", parsed.Query().Get("client_id"))
|
||||
assert.Equal(t, "code", parsed.Query().Get("response_type"))
|
||||
}
|
||||
|
||||
func TestNewMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -16,11 +16,9 @@ package complianceportal_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.gearno.de/x/ref"
|
||||
"go.probo.inc/probo/pkg/baseurl"
|
||||
@@ -123,17 +121,12 @@ func NewMux(cfg MuxConfig) (http.Handler, error) {
|
||||
)
|
||||
|
||||
r.Handle("/*", webServer)
|
||||
r.NotFound(handleCustomDomain404)
|
||||
},
|
||||
)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func handleCustomDomain404(w http.ResponseWriter, r *http.Request) {
|
||||
httpserver.RenderError(w, http.StatusNotFound, errors.New("not found"))
|
||||
}
|
||||
|
||||
func compliancePageHeadData() HeadDataFunc {
|
||||
return func(r *http.Request) HeadData {
|
||||
tc := complianceportal.CompliancePageFromContext(r.Context())
|
||||
|
||||
Reference in New Issue
Block a user