Skip to content

Commit

Permalink
HSP connector
Browse files Browse the repository at this point in the history
Signed-off-by: Andy Lo-A-Foe <[email protected]>
  • Loading branch information
loafoe committed Oct 4, 2024
1 parent 9e1ae0e commit 32d93b5
Show file tree
Hide file tree
Showing 9 changed files with 1,111 additions and 0 deletions.
89 changes: 89 additions & 0 deletions .github/workflows/artifacts-fork.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Fork Artifacts

on:
push:
branches:
- master
tags:
- '*'
pull_request:

jobs:
container-images:
name: Container images
runs-on: ubuntu-latest
strategy:
matrix:
variant:
- alpine
- distroless

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Gather metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
ghcr.io/philips-forks/dex
flavor: |
latest = false
tags: |
type=ref,event=branch,enable=${{ matrix.variant == 'alpine' }}
type=ref,event=pr,enable=${{ matrix.variant == 'alpine' }}
type=semver,pattern={{raw}},enable=${{ matrix.variant == 'alpine' }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && matrix.variant == 'alpine' }}
type=ref,event=branch,suffix=-${{ matrix.variant }}
type=ref,event=pr,suffix=-${{ matrix.variant }}
type=semver,pattern={{raw}},suffix=-${{ matrix.variant }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }},suffix=-${{ matrix.variant }}
labels: |
org.opencontainers.image.documentation=https://dexidp.io/docs/
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: all

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ github.token }}
if: github.event_name == 'push'

- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
# cache-from: type=gha
# cache-to: type=gha,mode=max
push: ${{ github.event_name == 'push' }}
tags: ${{ steps.meta.outputs.tags }}
build-args: |
BASE_IMAGE=${{ matrix.variant }}
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
COMMIT_HASH=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
labels: ${{ steps.meta.outputs.labels }}

- name: Run Trivy vulnerability scanner
uses: aquasecurity/[email protected]
with:
image-ref: "ghcr.io/philips-forks/dex:${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}"
format: "sarif"
output: "trivy-results.sarif"
if: github.event_name == 'push'

- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: "trivy-results.sarif"
if: github.event_name == 'push'
60 changes: 60 additions & 0 deletions connector/hsdp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# hsdp connector

This connector supports [HSP IAM](https://www.hsdp.io/documentation/identity-and-access-management-iam/getting-started) as an upstream IDP for Dex.

# helm chart

Dex is deployed using the [helm chart](https://artifacthub.io/packages/helm/dex/dex) from the [Artifact Hub](https://artifacthub.io/).

# configuration

When deploying Dex with the HSP IAM connector, you need to configure the connector in the Dex configuration file.
Helm chart users can configure the connector in the `values.yaml` file.

Connector section example:

```yaml
connectors:
- type: hsdp
id: hsdp
name: Philips Code1
config:
trustedOrgID: 8a67a785-73bb-46d5-b73f-d951a6d3cb43
tenantMap:
dae89cf0-888d-4a26-8c1d-578e97365efc: rpi5
8a67a785-73bb-46d5-b73f-d951a6d3cb43: starlift
issuer: 'https://iam-client-test.us-east.philips-healthsuite.com/authorize/oauth2/v2'
insecureIssuer: 'https://iam-client-test.us-east.philips-healthsuite.com/oauth2/access_token'
saml2LoginURL: 'https://iam-integration.us-east.philips-healthsuite.com/authorize/saml2/login?idp_id=https://sts.windows.net/1a407a2d-7675-4d17-8692-b3ac285306e4/&client_id=sp-philips-hspiam-useast-ct&api-version=1'
clientID: iamclient
clientSecret: SecretHere
iamURL: 'https://iam-client-test.us-east.philips-healthsuite.com'
idmURL: 'https://idm-client-test.us-east.philips-healthsuite.com'
redirectURI: https://dex.hsp.philips.com/callback
getUserInfo: true
userNameKey: sub
scopes:
- auth_iam_introspect
- auth_iam_organization
- openid
- profile
- email
- name
- federated:id
```
The following fields are supported:
| Config field | Type | Description |
|----------------|-------------|----------------------------------------------------------------------------|
| trustedOrgID | string | The HSP IAM OrgID to determine claims |
| tenantMap | map(string) | Mapping of OrgIDs to tenant IDs (Observability |
| issuer | string | The issuer URL of the HSP IAM deployment |
| insecureIssuer | string | the issuer as returnd by HSP IAM. These are different in current IAM (bug) |
| saml2LoginURL | string | The SAML login URL given by HSP IAM for SSO login (code1) |
| clientID | string | An HSP IAM OAuth2 client ID |
| clientSecret | string | An HSP IAM OAuth2 client secret |
| redirectURI | string | The redirect URI of your Dex deployment. PAth should be `/callback` |
| getUserInfo | bool | Wether to inject complete userInfo as a claim in the JWT Token |
| userNameKey | string | The username key. Should be set to `sub` |
| scopes | string | The scopes to send to HSP IAM |
102 changes: 102 additions & 0 deletions connector/hsdp/extend_payload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package hsdp

import (
"encoding/json"
"fmt"
"slices"
"strings"
)

func (c *HSDPConnector) ExtendPayload(scopes []string, payload []byte, cdata []byte) ([]byte, error) {
var cd ConnectorData
var originalClaims map[string]interface{}

if err := json.Unmarshal(cdata, &cd); err != nil {
return payload, err
}
if err := json.Unmarshal(payload, &originalClaims); err != nil {
return payload, err
}

c.logger.Info("ExtendPayload called", "sub", cd.Introspect.Sub, "user", cd.Introspect.Username)

// Service identity tokens should expire when the service identity token expires
if cd.Introspect.IdentityType == "Service" {
originalClaims["exp"] = cd.Introspect.Expires
originalClaims["username"] = cd.Introspect.Sub
originalClaims["preferred_username"] = cd.Introspect.Sub
}

for _, scope := range scopes {
// Experimental fill introspect body into claims
if scope == "hsp:iam:introspect" {
originalClaims["intr"] = cd.Introspect
}
// Experimental fill token into claims
if scope == "hsp:iam:token" {
originalClaims["tkn"] = string(cd.AccessToken)
}
}
originalClaims["idt"] = cd.Introspect.IdentityType
// Rewrite subject
var orgSubs []string
var orgGroups []string
var orgRoles []string
for _, org := range cd.Introspect.Organizations.OrganizationList {
for _, role := range org.Roles {
orgRoles = append(orgRoles, fmt.Sprintf("urn:iamr:%s:%s", org.OrganizationID, strings.ToLower(role)))
}
for _, group := range org.Groups {
orgGroups = append(orgGroups, fmt.Sprintf("urn:iamg:%s:%s", org.OrganizationID, strings.ToLower(group)))
}
}
if c.enableRoleClaim && len(orgRoles) > 0 {
originalClaims["roles"] = orgRoles
}
if c.enableGroupClaim && len(orgGroups) > 0 {
originalClaims["groups"] = orgGroups
}
if c.enableRoleClaim && c.roleAsGroupClaim && len(orgRoles) > 0 {
originalClaims["groups"] = orgRoles
delete(originalClaims, "roles")
}

// Rewrite name
if cd.User.GivenName != "" {
originalClaims["name"] = fmt.Sprintf("%s %s", cd.User.GivenName, cd.User.FamilyName)
}
// Inject username
if cd.Introspect.Username != "" {
originalClaims["username"] = cd.Introspect.Username
originalClaims["preferred_username"] = cd.Introspect.Username
}
if len(orgSubs) > 0 {
subs := strings.Join(orgSubs, ":")
origSub := originalClaims["sub"].(string)
originalClaims["sub"] = fmt.Sprintf("%s:id:%s", subs, origSub)
}

// Custom claims for Observability
var readTenants []string
// Collect all orgs for which the user has LOG.READ permission
for _, org := range cd.Introspect.Organizations.OrganizationList {
if slices.Contains(org.Permissions, "LOG.READ") {
readTenants = append(readTenants, mapper(org.OrganizationID, c.tenantMap))
}
}
if len(readTenants) > 0 {
originalClaims["ort"] = readTenants
}
extendedPayload, err := json.Marshal(originalClaims)
if err != nil {
return payload, err
}
return extendedPayload, nil
}

func mapper(src string, data map[string]string) string {
if orgID, ok := data[src]; ok {
return orgID
}
return src
}
Loading

0 comments on commit 32d93b5

Please sign in to comment.