8000 Add JWT and OIDC to Sentry by jjcollinge · Pull Request #8662 · dapr/dapr · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add JWT and OIDC to Sentry #8662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e5c6502
WIP: add initial support for jwt in sentry
jjcollinge Apr 12, 2025
555d882
WIP oidc
jjcollinge Apr 13, 2025
a286b12
Add integartion with test framework
jjcollinge Apr 13, 2025
92c5403
Add integration tests
jjcollinge Apr 13, 2025
ba0505f
Include go.mod
jjcollinge Apr 13, 2025
4cf5ac9
Integration tests
jjcollinge Apr 13, 2025
0c7c5cc
standalone test
jjcollinge Apr 14, 2025
854f84b
Add kubernetes test
jjcollinge Apr 14, 2025
16772ef
Fail on none ecdsa key
jjcollinge Apr 14, 2025
7dde958
Make comment clearer
jjcollinge Apr 14, 2025
a049d7d
Update go.mod
jjcollinge Apr 14, 2025
7456baf
Add use sig for azure
jjcollinge Apr 14, 2025
7d3690a
Add cloud audiences
jjcollinge Apr 14, 2025
8c26dfb
fix ca store test
jjcollinge Apr 14, 2025
ecb0013
Allow user to set extra audiences in token
jjcollinge Apr 15, 2025
01cd3bd
Add support for more keys
jjcollinge Apr 15, 2025
1cf4327
Add ca test
jjcollinge Apr 15, 2025
9a6f703
Add values to helm chart
jjcollinge Apr 15, 2025
cfe874f
Add insecure mode
jjcollinge Apr 16, 2025
b4c6840
Add jwt signing options
jjcollinge May 9, 2025
9b4f21c
make default extra audiences empty and add examples in comment
jjcollinge May 10, 2025
e7ebd4e
Update options
jjcollinge May 10, 2025
c3fbb06
Ensure key and alg compatibility
jjcollinge May 11, 2025
bc7a1e1
Fix standalone test
jjcollinge May 11, 2025
630fef7
Move audience to app config
jjcollinge May 18, 2025
f540fee
Add nonce to request
jjcollinge May 19, 2025
a19571b
Remove nonce from token
jjcollinge May 19, 2025
82caa3a
go get dapr/kit@main
jjcollinge May 23, 2025
787a1e8
go mod tidy
jjcollinge May 23, 2025
db84f3b
Remove newline in replace
jjcollinge May 23, 2025
4d1d7a9
Remove newline near replace block
jjcollinge May 23, 2025
c541673
feedback
jjcollinge May 23, 2025
c5d4ad4
Add annotation test case
jjcollinge May 29, 2025
6f795c9
Add name to healthz
jjcollinge Jun 23, 2025
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
1 change: 0 additions & 1 deletion .build-tools/cmd/zz-e2e-perf.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,6 @@ func hashFilesInDir(basePath string, ignores *gitignore.GitIgnore) ([]string, er
files = append(files, checksum)
return nil
})

if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ spec:
- name: debug
containerPort: {{ .Values.debug.port }}
protocol: TCP
{{- end }}
{{- if .Values.oidc.enabled }}
- name: oidc
containerPort: 9080
protocol: TCP
{{- end }}
resources:
{{ toYaml .Values.resources | indent 10 }}
Expand Down Expand Up @@ -188,6 +193,52 @@ spec:
- "--issuer-key-filename"
- "{{ .key }}"
{{- end }}
- "--jwt-enabled={{ .Values.jwt.enabled }}"
{{- if .Values.jwt.keyFilename }}
- "--jwt-key-filename={{ .Values.jwt.keyFilename }}"
{{- end }}
{{- if .Values.jwt.jwksFilename }}
- "--jwks-filename={{ .Values.jwt.jwksFilename }}"
{{- end }}
{{- if .Values.jwt.issuer }}
- "--jwt-issuer={{ .Values.jwt.issuer }}"
{{- end }}
{{- if .Values.jwt.signingAlgorithm }}
- "--jwt-signing-algorithm={{ .Values.jwt.signingAlgorithm }}"
{{- end }}
{{- if .Values.jwt.keyID }}
- "--jwt-key-id={{ .Values.jwt.keyID }}"
{{- end }}
{{- if .Values.oidc.enabled }}
- "--oidc-enabled=true"
{{- else }}
- "--oidc-enabled=false"
{{- end }}
{{- if and .Values.oidc.server .Values.oidc.server.port }}
- "--oidc-server-listen-port={{ .Values.oidc.server.port }}"
{{- end }}
{{- if and .Values.oidc.server .Values.oidc.server.address }}
- "--oidc-server-address={{ .Values.oidc.server.address }}"
{{- end }}
{{- if .Values.oidc.jwksURI }}
- "--oidc-jwks-uri={{ .Values.oidc.jwksURI }}"
{{- end }}
{{- if .Values.oidc.pathPrefix }}
- "--oidc-path-prefix={{ .Values.oidc.pathPrefix }}"
{{- end }}
{{- if .Values.oidc.allowedHosts }}
- "--oidc-allowed-hosts={{ join "," .Values.oidc.allowedHosts }}"
{{- end }}
{{- if .Values.oidc.tls.certFile }}
- "--oidc-server-tls-cert-file={{ .Values.oidc.tls.certFile }}"
{{- end }}
{{- if .Values.oidc.tls.keyFile }}
- "--oidc-server-tls-key-file={{ .Values.oidc.tls.keyFile }}"
{{- end }}
{{- if .Values.oidc.tls.enabled }}
- "--oidc-server-tls-enabled={{ .Values.oidc.tls.enabled }}"
{{- end }}

serviceAccountName: dapr-sentry
volumes:
- name: credentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ spec:
targetPort: {{ .Values.global.prometheus.port }}
protocol: TCP
{{- end}}
{{- if .Values.oidc.enabled }}
- name: oidc
port: {{ .Values.oidc.server.port }}
targetPort: 9080
protocol: TCP
{{- end }}
34 changes: 34 additions & 0 deletions charts/dapr/charts/dapr_sentry/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,40 @@ tls:
certPEM: ""
trustDomain: cluster.local

jwt:
# Enable JWT token issuance by Sentry
enabled: false
# JWT signing key filename
keyFilename: ""
# JWKS (JSON Web Key Set) filename
jwksFilename: ""
# Issuer value for JWT tokens (no issuer if empty)
issuer: ""
# Algorithm used for JWT signing (e.g., ES256, RS256)
signingAlgorithm: ""
# Key ID (kid) used for JWT signing
keyID: ""


oidc:
enabled: false
server:
# Port for the OIDC HTTP server
port: 9080
# Custom URI where the JWKS can be accessed externally
jwksURI: ""
# Path prefix to add to OIDC HTTP endpoints
pathPrefix: ""
# List of allowed hosts for OIDC HTTP endpoint requests
allowedHosts: []
tls:
# Enable TLS for the OIDC HTTP server
enabled: true
# TLS certificate file for the OIDC HTTP server (required when OIDC HTTP server is enabled)
certFile: ""
# TLS certificate file for the OIDC HTTP server (required when OIDC HTTP server is enabled)
keyFile: ""

livenessProbe:
initialDelaySeconds: 3
periodSeconds: 3
Expand Down
1 change: 1 addition & 0 deletions cmd/daprd/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func Run() {
MTLSEnabled: opts.EnableMTLS,
Mode: modes.DaprMode(opts.Mode),
Healthz: healthz,
JwtAudiences: opts.JwtAudiences,
})
if err != nil {
log.Fatal(err)
Expand Down
2 changes: 2 additions & 0 deletions cmd/daprd/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type Options struct {
DisableBuiltinK8sSecretStore bool
AppHealthCheckPath string
AppChannelAddress string
JwtAudiences []string
Logger logger.Options
Metrics *metrics.FlagOptions
}
Expand Down Expand Up @@ -138,6 +139,7 @@ func New(origArgs []string) (*Options, error) {
fs.StringVar(&opts.SentryAddress, "sentry-address", "", "Address for the Sentry CA service")
fs.StringVar(&opts.ControlPlaneTrustDomain, "control-plane-trust-domain", "localhost", "Trust domain of the Dapr control plane")
fs.StringVar(&opts.ControlPlaneNamespace, "control-plane-namespace", "default", "Namespace of the Dapr control plane")
fs.StringSliceVar(&opts.JwtAudiences, "jwt-audiences", nil, "JWT audience list for certificate signing requests. If not specified, the trust domain will be used")
fs.StringVar(&opts.AllowedOrigins, "allowed-origins", cors.DefaultAllowedOrigins, "Allowed HTTP origins")
fs.BoolVar(&opts.EnableProfiling, "enable-profiling", false, "Enable profiling")
fs.BoolVar(&opts.RuntimeVersion, "version", false, "Prints the runtime version")
Expand Down
104 changes: 88 additions & 16 deletions cmd/sentry/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package app

import (
"context"
"crypto/tls"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -67,30 +68,49 @@ func Run() {
mngr = concurrency.NewRunnerManager()
)

issuerCertPath := filepath.Join(opts.IssuerCredentialsPath, opts.IssuerCertFilename)
if filepath.IsAbs(opts.IssuerCertFilename) {
log.Debugf("Using user provided issuer cert path: %s", opts.IssuerCertFilename)
issuerCertPath = opts.IssuerCertFilename
issuerCertPath := filepath.Join(opts.IssuerCredentialsPath, opts.X509.IssuerCertFilename)
if filepath.IsAbs(opts.X509.IssuerCertFilename) {
log.Debugf("Using user provided issuer cert path: %s", opts.X509.IssuerCertFilename)
issuerCertPath = opts.X509.IssuerCertFilename
}
issuerKeyPath := filepath.Join(opts.IssuerCredentialsPath, opts.IssuerKeyFilename)
if filepath.IsAbs(opts.IssuerKeyFilename) {
log.Debugf("Using user provided issuer key path: %s", opts.IssuerKeyFilename)
issuerKeyPath = opts.IssuerKeyFilename
issuerKeyPath := filepath.Join(opts.IssuerCredentialsPath, opts.X509.IssuerKeyFilename)
if filepath.IsAbs(opts.X509.IssuerKeyFilename) {
log.Debugf("Using user provided issuer key path: %s", opts.X509.IssuerKeyFilename)
issuerKeyPath = opts.X509.IssuerKeyFilename
}
rootCertPath := filepath.Join(opts.IssuerCredentialsPath, opts.RootCAFilename)
if filepath.IsAbs(opts.RootCAFilename) {
log.Debugf("Using user provided root cert path: %s", opts.RootCAFilename)
rootCertPath = opts.RootCAFilename
rootCertPath := filepath.Join(opts.IssuerCredentialsPath, opts.X509.RootCAFilename)
if filepath.IsAbs(opts.X509.RootCAFilename) {
log.Debugf("Using user provided root cert path: %s", opts.X509.RootCAFilename)
rootCertPath = opts.X509.RootCAFilename
}
jwtKeyPath := filepath.Join(opts.IssuerCredentialsPath, config.DefaultJWTSigningKeyFilename)
if filepath.IsAbs(opts.JWT.SigningKeyFilename) {
log.Debugf("Using user provided JWT signing key path: %s", opts.JWT.SigningKeyFilename)
jwtKeyPath = opts.JWT.SigningKeyFilename
}
jwksPath := filepath.Join(opts.IssuerCredentialsPath, config.DefaultJWKSFilename)
if filepath.IsAbs(opts.JWT.JWKSFilename) {
log.Debugf("Using user provided JWKS path: %s", opts.JWT.JWKSFilename)
jwksPath = opts.JWT.JWKSFilename
}

m := make(map[string]struct{})
// we need to watch over all these relevant directories
for _, path := range []string{issuerCertPath, issuerKeyPath, rootCertPath} {
dir := filepath.Dir(path)
if _, ok := m[dir]; !ok {
m[dir] = struct{}{}
for _, path := range []*string{
&issuerCertPath,
&issuerKeyPath,
&rootCertPath,
&jwtKeyPath,
&jwksPath,
} {
if path != nil {
dir := filepath.Dir(*path)
if _, ok := m[dir]; !ok {
m[dir] = struct{}{}
}
}
}

watchDirs := make([]string, 0, len(m))
for dir := range m {
watchDirs = append(watchDirs, dir)
Expand All @@ -104,11 +124,28 @@ func Run() {
cfg.IssuerCertPath = issuerCertPath
cfg.IssuerKeyPath = issuerKeyPath
cfg.RootCertPath = rootCertPath
cfg.JWT.SigningKeyPath = jwtKeyPath
cfg.JWT.Enabled = opts.JWT.Enabled
cfg.JWT.JWKSPath = jwksPath
cfg.TrustDomain = opts.TrustDomain
cfg.Port = opts.Port
cfg.ListenAddress = opts.ListenAddress
cfg.Mode = modes.DaprMode(opts.Mode)

if opts.JWT.Issuer != nil {
cfg.JWT.Issuer = opts.JWT.Issuer
}

if opts.JWT.SigningAlgorithm != "" {
cfg.JWT.SigningAlgorithm = opts.JWT.SigningAlgorithm
}

if opts.JWT.KeyID != nil {
cfg.JWT.KeyID = opts.JWT.KeyID
}

cfg.JWT.TTL = opts.JWT.TTL

// We use runner manager inception here since we want the inner manager to be
// restarted when the CA server needs to be restarted because of file events.
// We don't want to restart the healthz server and file watcher on file
Expand All @@ -122,9 +159,31 @@ func Run() {
Port: opts.Metrics.Port(),
Healthz: healthz,
})

// Configure TLS for OIDC HTTP server if needed
var oidcTLSConfig *tls.Config
if opts.OIDC.TLSCertFile != "" && opts.OIDC.TLSKeyFile != "" {
oidcTLSConfig, err = createOIDCTLSConfig(opts.OIDC.TLSCertFile, opts.OIDC.TLSKeyFile)
if err != nil {
log.Errorf("Failed to create OIDC TLS config: %v", err)
return err
}
} else if opts.OIDC.TLSCertFile != "" || opts.OIDC.TLSKeyFile != "" {
log.Fatalf("Both OIDC TLS certificate and key must be provided if one is specified. Cert: %q, Key: %q", opts.OIDC.TLSCertFile, opts.OIDC.TLSKeyFile)
}

sentry, serr := sentry.New(ctx, sentry.Options{
Config: cfg,
Healthz: healthz,
OIDC: sentry.OIDCOptions{
Enabled: opts.OIDC.Enabled,
ServerListenAddress: opts.OIDC.ServerListenAddress,
ServerListenPort: opts.OIDC.ServerListenPort,
JWKSURI: opts.OIDC.JWKSURI,
PathPrefix: opts.OIDC.PathPrefix,
Domains: opts.OIDC.AllowedHosts,
TLSConfig: oidcTLSConfig,
},
})
if serr != nil {
return serr
Expand Down Expand Up @@ -197,3 +256,16 @@ func Run() {
}
log.Info("Sentry shut down gracefully")
}

// createOIDCTLSConfig creates a TLS configuration for the OIDC HTTP server
func createOIDCTLSConfig(certFile, keyFile string) (*tls.Config, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be the identity cert, but in any case, we should be passing down the field paths to the TLS config, not reading them manually. This means Sentry doesn't need to restart in the event the file changes, (I think!).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to help me on this one please - is there a library function I should be using here or overriding GetCertificate?

cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}

return &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}, nil
}
Loading
Loading
0