diff --git a/.github/workflows/artifacts.yaml b/.github/workflows/artifacts.yaml index b9ed33fc..fdd4e88f 100644 --- a/.github/workflows/artifacts.yaml +++ b/.github/workflows/artifacts.yaml @@ -61,7 +61,7 @@ jobs: uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 - name: Set up Syft - uses: anchore/sbom-action/download-syft@1ca97d9028b51809cf6d3c934c3e160716e1b605 # v0.17.5 + uses: anchore/sbom-action/download-syft@55dc4ee22412511ee8c3142cbea40418e6cec693 # v0.17.8 - name: Install cosign uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 @@ -192,12 +192,38 @@ jobs: push-to-registry: true if: inputs.publish + ## Use cache for the trivy-db to avoid the TOOMANYREQUESTS error https://github.com/aquasecurity/trivy-action/pull/397 + ## To avoid the trivy-db becoming outdated, we save the cache for one day + - name: Get data + id: date + run: echo "date=$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT + + - name: Restore trivy cache + uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + with: + path: cache/db + key: trivy-cache-${{ steps.date.outputs.date }} + restore-keys: + trivy-cache- + - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 with: input: image format: sarif output: trivy-results.sarif + scan-type: 'fs' + scan-ref: '.' + cache-dir: "./cache" + env: + TRIVY_SKIP_DB_UPDATE: true + TRIVY_SKIP_JAVA_DB_UPDATE: true + + ## Trivy-db uses `0600` permissions. + ## But `action/cache` use `runner` user by default + ## So we need to change the permissions before caching the database. + - name: change permissions for trivy.db + run: sudo chmod 0644 ./cache/db/trivy.db - name: Upload Trivy scan results as artifact uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 215cdf01..c20f31b9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -69,7 +69,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Go - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version: "1.21" @@ -140,7 +140,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Go - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version: "1.21" @@ -175,4 +175,4 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Dependency Review - uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5 + uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 diff --git a/.github/workflows/trivydb-cache.yaml b/.github/workflows/trivydb-cache.yaml new file mode 100644 index 00000000..e99b4170 --- /dev/null +++ b/.github/workflows/trivydb-cache.yaml @@ -0,0 +1,39 @@ +# Note: This workflow only updates the cache. You should create a separate workflow for your actual Trivy scans. +# In your scan workflow, set TRIVY_SKIP_DB_UPDATE=true and TRIVY_SKIP_JAVA_DB_UPDATE=true. +name: Update Trivy Cache + +on: + schedule: + - cron: '0 0 * * *' # Run daily at midnight UTC + workflow_dispatch: # Allow manual triggering + +jobs: + update-trivy-db: + runs-on: ubuntu-latest + steps: + - name: Setup oras + uses: oras-project/setup-oras@9c92598691bfef1424de2f8fae81941568f5889c # v1.2.1 + + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + - name: Download and extract the vulnerability DB + run: | + mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db + oras pull ghcr.io/aquasecurity/trivy-db:2 + tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db + rm db.tar.gz + + - name: Download and extract the Java DB + run: | + mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db + oras pull ghcr.io/aquasecurity/trivy-java-db:1 + tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db + rm javadb.tar.gz + + - name: Cache DBs + uses: actions/cache/save@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + with: + path: ${{ github.workspace }}/.cache/trivy + key: cache-trivy-${{ steps.date.outputs.date }} diff --git a/Dockerfile b/Dockerfile index a4039a18..9395153f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ ARG BASE_IMAGE=alpine -FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0@sha256:0c6a569797744e45955f39d4f7538ac344bfb7ebf0a54006a0a4297b153ccf0f AS xx +FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.6.1@sha256:923441d7c25f1e2eb5789f82d987693c47b8ed987c4ab3b075d6ed2b5d6779a3 AS xx -FROM --platform=$BUILDPLATFORM golang:1.23.2-alpine3.20@sha256:9dd2625a1ff2859b8d8b01d8f7822c0f528942fe56cfe7a1e7c38d3b8d72d679 AS builder +FROM --platform=$BUILDPLATFORM golang:1.23.3-alpine3.20@sha256:c694a4d291a13a9f9d94933395673494fc2cc9d4777b85df3a7e70b3492d3574 AS builder COPY --from=xx / / @@ -35,13 +35,13 @@ RUN make release-binary RUN xx-verify /go/bin/dex && xx-verify /go/bin/docker-entrypoint -FROM alpine:3.20.3@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d AS stager +FROM alpine:3.20.3@sha256:1e42bbe2508154c9126d48c2b8a75420c3544343bf86fd041fb7527e017a4b4a AS stager RUN mkdir -p /var/dex RUN mkdir -p /etc/dex COPY config.docker.yaml /etc/dex/ -FROM alpine:3.20.3@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d AS gomplate +FROM alpine:3.20.3@sha256:1e42bbe2508154c9126d48c2b8a75420c3544343bf86fd041fb7527e017a4b4a AS gomplate ARG TARGETOS ARG TARGETARCH @@ -54,8 +54,8 @@ RUN wget -O /usr/local/bin/gomplate \ && chmod +x /usr/local/bin/gomplate # For Dependabot to detect base image versions -FROM alpine:3.20.3@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d AS alpine -FROM gcr.io/distroless/static-debian12:nonroot@sha256:26f9b99f2463f55f20db19feb4d96eb88b056e0f1be7016bb9296a464a89d772 AS distroless +FROM alpine:3.20.3@sha256:1e42bbe2508154c9126d48c2b8a75420c3544343bf86fd041fb7527e017a4b4a AS alpine +FROM gcr.io/distroless/static-debian12:nonroot@sha256:d71f4b239be2d412017b798a0a401c44c3049a3ca454838473a4c32ed076bfea AS distroless FROM $BASE_IMAGE diff --git a/README.md b/README.md index 2894dcdd..dac886ee 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Dex implements the following connectors: | [AuthProxy](https://dexidp.io/docs/connectors/authproxy/) | no | yes | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. | | [Bitbucket Cloud](https://dexidp.io/docs/connectors/bitbucketcloud/) | yes | yes | no | alpha | | | [OpenShift](https://dexidp.io/docs/connectors/openshift/) | yes | yes | no | alpha | | -| [Atlassian Crowd](https://dexidp.io/docs/connectors/atlassiancrowd/) | yes | yes | yes * | beta | preferred_username claim must be configured through config | +| [Atlassian Crowd](https://dexidp.io/docs/connectors/atlassian-crowd/) | yes | yes | yes * | beta | preferred_username claim must be configured through config | | [Gitea](https://dexidp.io/docs/connectors/gitea/) | yes | no | yes | beta | | | [OpenStack Keystone](https://dexidp.io/docs/connectors/keystone/) | yes | yes | no | alpha | | diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index 7d0cacb0..1ea0c1fc 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -23,7 +23,12 @@ import ( // Config holds configuration options for OpenID Connect logins. type Config struct { - Issuer string `json:"issuer"` + Issuer string `json:"issuer"` + // Some offspec providers like Azure, Oracle IDCS have oidc discovery url + // different from issuer url which causes issuerValidation to fail + // IssuerAlias provides a way to override the Issuer url + // from the .well-known/openid-configuration issuer + IssuerAlias string `json:"issuerAlias"` ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` @@ -226,7 +231,9 @@ func (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, bgctx, cancel := context.WithCancel(context.Background()) ctx := context.WithValue(bgctx, oauth2.HTTPClient, httpClient) - + if c.IssuerAlias != "" { + ctx = oidc.InsecureIssuerURLContext(ctx, c.IssuerAlias) + } provider, err := getProvider(ctx, c.Issuer, c.ProviderDiscoveryOverrides) if err != nil { cancel() @@ -540,6 +547,13 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I continue } groups = append(groups, s) + } else if groupMap, ok := v.(map[string]interface{}); ok { + if s, ok := groupMap["name"].(string); ok { + if c.groupsFilter != nil && !c.groupsFilter.MatchString(s) { + continue + } + groups = append(groups, s) + } } else { return identity, fmt.Errorf("malformed \"%v\" claim", groupsKey) } diff --git a/connector/oidc/oidc_test.go b/connector/oidc/oidc_test.go index 66b35c3f..e31d4e0b 100644 --- a/connector/oidc/oidc_test.go +++ b/connector/oidc/oidc_test.go @@ -292,6 +292,38 @@ func TestHandleCallback(t *testing.T) { "email_verified": true, }, }, + { + name: "singularGroupResponseAsMap", + userIDKey: "", // not configured + userNameKey: "", // not configured + expectUserID: "subvalue", + expectUserName: "namevalue", + expectGroups: []string{"group1"}, + expectedEmailField: "emailvalue", + token: map[string]interface{}{ + "sub": "subvalue", + "name": "namevalue", + "groups": []map[string]string{{"name": "group1"}}, + "email": "emailvalue", + "email_verified": true, + }, + }, + { + name: "multipleGroupResponseAsMap", + userIDKey: "", // not configured + userNameKey: "", // not configured + expectUserID: "subvalue", + expectUserName: "namevalue", + expectGroups: []string{"group1", "group2"}, + expectedEmailField: "emailvalue", + token: map[string]interface{}{ + "sub": "subvalue", + "name": "namevalue", + "groups": []map[string]string{{"name": "group1"}, {"name": "group2"}}, + "email": "emailvalue", + "email_verified": true, + }, + }, { name: "newGroupFromClaims", userIDKey: "", // not configured @@ -382,6 +414,23 @@ func TestHandleCallback(t *testing.T) { "email_verified": true, }, }, + { + name: "filterGroupClaimsMap", + userIDKey: "", // not configured + userNameKey: "", // not configured + groupsRegex: `^.*\d$`, + expectUserID: "subvalue", + expectUserName: "namevalue", + expectGroups: []string{"group1", "group2"}, + expectedEmailField: "emailvalue", + token: map[string]interface{}{ + "sub": "subvalue", + "name": "namevalue", + "groups": []map[string]string{{"name": "group1"}, {"name": "group2"}, {"name": "groupA"}, {"name": "groupB"}}, + "email": "emailvalue", + "email_verified": true, + }, + }, } for _, tc := range tests { diff --git a/examples/example-app/templates.go b/examples/example-app/templates.go index a9425ead..7107eb87 100644 --- a/examples/example-app/templates.go +++ b/examples/example-app/templates.go @@ -6,38 +6,225 @@ import ( "net/http" ) +const css = ` + body { + font-family: Arial, sans-serif; + background-color: #f2f2f2; + margin: 0; + } + + .header { + text-align: center; + margin-bottom: 20px; + } + + .dex { + font-size: 2em; + font-weight: bold; + color: #3F9FD8; /* Main color */ + } + + .example-app { + font-size: 1em; + color: #EF4B5C; /* Secondary color */ + } + + .form-instructions { + text-align: center; + margin-bottom: 15px; + font-size: 1em; + color: #555; + } + + hr { + border: none; + border-top: 1px solid #ccc; + margin-top: 10px; + margin-bottom: 20px; + } + + label { + flex: 1; + font-weight: bold; + color: #333; + } + + p { + margin-bottom: 15px; + display: flex; + align-items: center; + } + + input[type="text"] { + flex: 2; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + outline: none; + } + + input[type="checkbox"] { + margin-left: 10px; + transform: scale(1.2); + } + + .back-button { + display: inline-block; + padding: 8px 16px; + background-color: #EF4B5C; /* Secondary color */ + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + text-decoration: none; + transition: background-color 0.3s ease, transform 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + position: fixed; + right: 20px; + bottom: 20px; + } + + .back-button:hover { + background-color: #C43B4B; /* Darker shade of secondary color */ + } + + .token-block { + background-color: #fff; + padding: 10px 15px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 15px; + word-wrap: break-word; + display: flex; + flex-direction: column; + gap: 5px; + position: relative; + } + + .token-title { + font-weight: bold; + display: flex; + justify-content: space-between; + align-items: center; + } + + .token-title a { + font-size: 0.9em; + text-decoration: none; + color: #3F9FD8; /* Main color */ + } + + .token-title a:hover { + text-decoration: underline; + } + + .token-code { + overflow-wrap: break-word; + word-break: break-all; + white-space: normal; + } + + pre { + white-space: pre-wrap; + background-color: #f9f9f9; + padding: 8px; + border-radius: 4px; + border: 1px solid #ddd; + margin: 0; + font-family: 'Courier New', Courier, monospace; + overflow-x: auto; + font-size: 0.9em; + position: relative; + margin-top: 5px; + } + + pre .key { + color: #c00; + } + + pre .string { + color: #080; + } + + pre .number { + color: #00f; + } +` + var indexTmpl = template.Must(template.New("index.html").Parse(` -
+ + + + + +ID Token:
{{ .IDToken }}
- Access Token:
{{ .AccessToken }}
- Claims:
{{ .Claims }}
- {{ if .RefreshToken }}
- Refresh Token:
{{ .RefreshToken }}
-
- {{ end }}
-
+
+
+ {{ if .IDToken }}
+ {{ .IDToken }}
+ {{ .AccessToken }}
+ {{ .Claims }}
+ {{ .RefreshToken }}
+
+