Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .github/workflows/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ jobs:
# Pinned to @main intentionally: org-internal workflows propagate updates automatically.
uses: kagenti/.github/.github/workflows/add-to-project.yml@main
secrets: inherit

2 changes: 1 addition & 1 deletion LOCAL_TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -757,4 +757,4 @@ kubectl run test-curl --rm -i --image=curlimages/curl --restart=Never -- sh -c "
jq '.[] | select(.clientId | contains(\"spiffe\")) | {clientId, clientAuthenticatorType}'
"
# Expected: "clientAuthenticatorType": "federated-jwt"
```
```
2 changes: 1 addition & 1 deletion authbridge/authproxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ COPY --from=builder /app/auth-proxy .

EXPOSE 8080

CMD ["./auth-proxy"]
CMD ["./auth-proxy"]
2 changes: 1 addition & 1 deletion authbridge/authproxy/Dockerfile.init
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ RUN chmod +x /usr/local/bin/init-iptables.sh

USER root

ENTRYPOINT ["/usr/local/bin/init-iptables.sh"]
ENTRYPOINT ["/usr/local/bin/init-iptables.sh"]
2 changes: 1 addition & 1 deletion authbridge/authproxy/quickstart/demo-app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ COPY --from=builder /app/target .

EXPOSE 8081 8443

CMD ["./target"]
CMD ["./target"]
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,3 @@ spec:
targetPort: 8443
name: https
type: ClusterIP

147 changes: 81 additions & 66 deletions authbridge/authproxy/quickstart/setup_keycloak.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
from keycloak import KeycloakAdmin, KeycloakGetError, KeycloakPostError
from keycloak import KeycloakAdmin, KeycloakPostError

KEYCLOAK_URL = "http://keycloak.localtest.me:8080"
KEYCLOAK_REALM = "kagenti"
KEYCLOAK_ADMIN_USERNAME = "admin"
KEYCLOAK_ADMIN_PASSWORD = "admin"


# Helper functions
def get_or_create_user(keycloak_admin, username):
users = keycloak_admin.get_users({"username": username})
user_id = None
if users:
# Filter strictly because search is fuzzy
existing_user = next((u for u in users if u['username'] == username), None)
existing_user = next((u for u in users if u["username"] == username), None)
if existing_user:
user_id = existing_user['id']
user_id = existing_user["id"]
print(f"User '{username}' already exists.")
if not user_id:
user_id = keycloak_admin.create_user({
"username": username,
"enabled": True,
"email": f"{username}@test.com",
"emailVerified": True,
"firstName": username,
"lastName": username,
}, True)
user_id = keycloak_admin.create_user(
{
"username": username,
"enabled": True,
"email": f"{username}@test.com",
"emailVerified": True,
"firstName": username,
"lastName": username,
},
True,
)
print(f"Created user '{username}'.")
return user_id


def get_or_create_client(keycloak_admin, client_payload):
existing_client_id = keycloak_admin.get_client_id(client_payload["clientId"])
if existing_client_id:
print(f"Client '{client_payload["clientId"]}' already exists.")
print(f"Client '{client_payload['clientId']}' already exists.")
return existing_client_id
client_id = keycloak_admin.create_client(client_payload)
print(f"Created client '{client_payload["clientId"]}'.")
print(f"Created client '{client_payload['clientId']}'.")
return client_id


def get_or_create_client_scope(keycloak_admin, scope_payload):
"""
Creates a client scope if it doesn't exist, or returns the ID of the existing one.
"""
scope_name = scope_payload.get("name")

# Keycloak python wrapper doesn't have a direct 'get_scope_id', so we list and filter
scopes = keycloak_admin.get_client_scopes()
for scope in scopes:
if scope['name'] == scope_name:
if scope["name"] == scope_name:
print(f"Client scope '{scope_name}' already exists with ID: {scope['id']}")
return scope['id']
return scope["id"]

# Create new scope
try:
Expand All @@ -58,6 +64,7 @@
print(f"Could not create client scope '{scope_name}': {e}")
raise


def add_audience_mapper(keycloak_admin, scope_id, mapper_name, audience):
"""
Adds an audience protocol mapper to a client scope if it doesn't already exist.
Expand All @@ -73,24 +80,25 @@
"included.custom.audience": audience,
"id.token.claim": "false",
"access.token.claim": "true",
"userinfo.token.claim": "false"
}
"userinfo.token.claim": "false",
},
}

try:
keycloak_admin.add_mapper_to_client_scope(scope_id, mapper_payload)
print(f"Added audience mapper '{mapper_name}' for audience '{audience}'")
except Exception as e:
print(f"Failed to add mapper '{mapper_name}': {e}")


# initialize keycloak admin client
print(f"Connecting to Keycloak at {KEYCLOAK_URL} as {KEYCLOAK_ADMIN_USERNAME}...")
keycloak_admin = KeycloakAdmin(
server_url=KEYCLOAK_URL,
username=KEYCLOAK_ADMIN_USERNAME,
password=KEYCLOAK_ADMIN_PASSWORD,
realm_name=KEYCLOAK_REALM,
user_realm_name="master"
user_realm_name="master",
)

# create test-user
Expand All @@ -100,58 +108,65 @@
print(f"Set password for '{test_user_name}'.")

# Create application-caller Client
app_caller_id = get_or_create_client(keycloak_admin, {
"clientId": "application-caller",
"name": "Application Caller",
"enabled": True,
"publicClient": False, # Creates a confidential client (Client Auth)
"directAccessGrantsEnabled": True,
"standardFlowEnabled": False
})
app_caller_id = get_or_create_client(
keycloak_admin,
{
"clientId": "application-caller",
"name": "Application Caller",
"enabled": True,
"publicClient": False, # Creates a confidential client (Client Auth)
"directAccessGrantsEnabled": True,
"standardFlowEnabled": False,
},
)

# Create authproxy Client
authproxy_id = get_or_create_client(keycloak_admin, {
"clientId": "authproxy",
"name": "Auth Proxy",
"enabled": True,
"publicClient": False, # Confidential client
"standardFlowEnabled": False,
"serviceAccountsEnabled": True,
"attributes": {
"standard.token.exchange.enabled": "true"
}
})
authproxy_id = get_or_create_client(
keycloak_admin,
{
"clientId": "authproxy",
"name": "Auth Proxy",
"enabled": True,
"publicClient": False, # Confidential client
"standardFlowEnabled": False,
"serviceAccountsEnabled": True,
"attributes": {"standard.token.exchange.enabled": "true"},
},
)

# Create demoapp Client (target service for token exchange)
demoapp_id = get_or_create_client(keycloak_admin, {
"clientId": "demoapp",
"name": "Demo App",
"enabled": True,
"publicClient": False, # Confidential client
"standardFlowEnabled": False,
"serviceAccountsEnabled": True,
})
demoapp_id = get_or_create_client(
keycloak_admin,
{
"clientId": "demoapp",
"name": "Demo App",
"enabled": True,
"publicClient": False, # Confidential client
"standardFlowEnabled": False,
"serviceAccountsEnabled": True,
},
)

# Create `authproxy-aud` Client scope
authproxy_scope_id = get_or_create_client_scope(keycloak_admin, {
"name": "authproxy-aud",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true"
}
})
authproxy_scope_id = get_or_create_client_scope(
keycloak_admin,
{
"name": "authproxy-aud",
"protocol": "openid-connect",
"attributes": {"include.in.token.scope": "true", "display.on.consent.screen": "true"},
},
)
add_audience_mapper(keycloak_admin, authproxy_scope_id, "authproxy-aud", "authproxy")

# Create `demoapp-aud` Client scope
demoapp_scope_id = get_or_create_client_scope(keycloak_admin, {
"name": "demoapp-aud",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true"
}
})
demoapp_scope_id = get_or_create_client_scope(
keycloak_admin,
{
"name": "demoapp-aud",
"protocol": "openid-connect",
"attributes": {"include.in.token.scope": "true", "display.on.consent.screen": "true"},
},
)
add_audience_mapper(keycloak_admin, demoapp_scope_id, "demoapp-aud", "demoapp")

# Assign default scopes
Expand All @@ -171,9 +186,9 @@

print("-" * 50)
try:
secret = keycloak_admin.get_client_secrets(app_caller_id)['value']
print(f"Run the following command to set the client secret:")
secret = keycloak_admin.get_client_secrets(app_caller_id)["value"]
print("Run the following command to set the client secret:")
print(f"export CLIENT_SECRET={secret}")

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
except Exception as e:
print(f"Could not retrieve secret: {e}")
print("-" * 50)
print("-" * 50)
16 changes: 8 additions & 8 deletions authbridge/client-registration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,18 @@ sequenceDiagram
Note over Helper,SPIRE: Pod Startup
SPIRE->>Helper: Issue JWT SVID
Helper->>Helper: Write to /opt/jwt_svid.token

Reg->>Reg: Wait for SVID file
Reg->>Reg: Extract SPIFFE ID from JWT

Reg->>KC: Register client (SPIFFE ID)
KC-->>Reg: Client created

Reg->>KC: Get client secret
KC-->>Reg: Secret value

Reg->>Reg: Write to /shared/client-secret.txt

App->>App: Read secret from shared volume
App->>KC: Authenticate with credentials
```
Expand Down Expand Up @@ -183,7 +183,7 @@ spec:
volumeMounts:
- name: shared-data
mountPath: /shared

# SPIFFE Helper - obtains SVID from SPIRE
- name: spiffe-helper
image: ghcr.io/spiffe/spiffe-helper:nightly
Expand All @@ -195,7 +195,7 @@ spec:
mountPath: /spiffe-workload-api
- name: svid-output
mountPath: /opt

# Client Registration - registers with Keycloak
- name: client-registration
image: ghcr.io/kagenti/kagenti-extensions/client-registration:latest
Expand Down Expand Up @@ -226,7 +226,7 @@ spec:
mountPath: /shared
- name: svid-output
mountPath: /opt

volumes:
- name: shared-data
emptyDir: {}
Expand Down
Loading
Loading