diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/api-services-portal-bug-issue-template.md similarity index 86% rename from .github/ISSUE_TEMPLATE/issue.md rename to .github/ISSUE_TEMPLATE/api-services-portal-bug-issue-template.md index c9cf2203f..8bfdf401b 100644 --- a/.github/ISSUE_TEMPLATE/issue.md +++ b/.github/ISSUE_TEMPLATE/api-services-portal-bug-issue-template.md @@ -1,9 +1,13 @@ --- -name: API Services Portal Issue Template -about: Generic template for all GitHub issues +name: API Services Portal Bug Issue Template +about: Generic template for bug GitHub issues +title: '' +labels: bug, jira +assignees: '' + --- -# API Services Portal Issue +# API Services Portal Bug Issue diff --git a/.github/ISSUE_TEMPLATE/api-services-portal-feature-request-issue-template.md b/.github/ISSUE_TEMPLATE/api-services-portal-feature-request-issue-template.md new file mode 100644 index 000000000..bb70f1f5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/api-services-portal-feature-request-issue-template.md @@ -0,0 +1,56 @@ +--- +name: API Services Portal Feature Request Issue Template +about: Generic template for feature request GitHub issues +title: '' +labels: enhancement, jira +assignees: '' + +--- + +## User Story + +**As a** ``, +**I want** ``, +**so that** ``. + +--- + +## Background +Provide a detailed description of the user story. Include any relevant background information and additional context. + +--- + +## Acceptance Criteria + +Choose one of the following formats and delete the rest. You may write your acceptance criteria in your preferred format as well. + + + - Given ``, when ``, then ``. + +- **Functional format**: + - The system should ``. + + - When ``, the system should ``. + +- **Other considerations**: + - Any other specific aspect that should be met for the story to be considered complete. + +--- + +## Business Value (Optional) +Explain the business value of this user story and how it aligns with the overall goals. + +--- + +## Assumptions (Optional) +List any assumptions that are considered as part of the story. + +--- + +## Out of Scope (Optional) +Specify anything that is out of scope for this user story. + +--- + +## Constraints (Optional) +List any constraints or limitations that must be considered. diff --git a/.github/workflows/ci-build-deploy.yaml b/.github/workflows/ci-build-deploy.yaml index 4f9aa28a8..4a98dc5ab 100644 --- a/.github/workflows/ci-build-deploy.yaml +++ b/.github/workflows/ci-build-deploy.yaml @@ -197,29 +197,97 @@ jobs: oauthProxy: enabled: true - image: - repository: ${{ env.REGISTRY }}/bcgov-dss/api-serv-infra/oauth2-proxy - tag: 7.2.1-8c743f0c - pullPolicy: IfNotPresent config: - upstream: http://127.0.0.1:3000 - client-id: ${{ secrets.OIDC_CLIENT_ID }} - client-secret: ${{ secrets.OIDC_CLIENT_SECRET }} - oidc-issuer-url: ${{ secrets.OIDC_ISSUER }} - redirect-url: https://api-services-portal-${{ steps.set-deploy-id.outputs.DEPLOY_ID }}.apps.silver.devops.gov.bc.ca/oauth2/callback - skip-auth-regex: '/login|/health|/public|/docs|/redirect|/_next|/images|/devportal|/manager|/about|/maintenance|/admin/session|/ds/api|/feed/|/signout|/content|^[/]$' - whitelist-domain: authz-apps-gov-bc-ca.dev.api.gov.bc.ca - skip-provider-button: 'true' - profile-url: ${{ secrets.OIDC_ISSUER }}/protocol/openid-connect/userinfo - insecure-oidc-allow-unverified-email: 'true' - oidc-email-claim: 'sub' - pass-basic-auth: 'false' - pass-access-token: 'true' - set-xauthrequest: 'true' - skip-jwt-bearer-tokens: 'false' - set-authorization-header: 'false' - pass-authorization-header: 'false' + - filename: oauth2-proxy.cfg + mountPath: /oauth2-proxy.cfg + contents: |- + cookie_expire='24h' + cookie_refresh='3m' + cookie_secure='true' + cookie_samesite='strict' + cookie_secret='not_secretenough' + email_domains='*' + redirect_url='https://api-services-portal-${{ steps.set-deploy-id.outputs.DEPLOY_ID }}.apps.silver.devops.gov.bc.ca/oauth2/callback' + skip_auth_regex='/login|/health|/public|/docs|/redirect|/_next|/images|/devportal|/manager|/about|/maintenance|/admin/session|/ds/api|/feed|/metrics|/signout|/gw/api|/content|^[/]$' + skip_jwt_bearer_tokens='false' + skip_provider_button='true' + whitelist_domains='authz-apps-gov-bc-ca.dev.api.gov.bc.ca' + # redis_connection_url="redis://redis-headless:6379" + # session_store_type="redis" + # redis_password="" + # insecure-oidc-allow-unverified-email: 'true' + # insecure-oidc-skip-issuer-verification: 'true' + # oidc-email-claim: 'sub' + # pass-authorization-header: 'false' + # set-authorization-header: 'false' + + - filename: oauth2-proxy.yaml + mountPath: /oauth2-proxy.yaml + yaml: + injectRequestHeaders: + - name: X-Forwarded-Groups + values: + - claim: groups + - name: X-Forwarded-User + values: + - claim: user + - name: X-Forwarded-Email + values: + - claim: email + - name: X-Forwarded-Preferred-Username + values: + - claim: preferred_username + - name: X-Forwarded-Access-Token + values: + - claim: access_token + injectResponseHeaders: [] + metricsServer: + BindAddress: "" + SecureBindAddress: "" + TLS: null + providers: + - clientID: ${{ secrets.OIDC_CLIENT_ID }} + clientSecret: ${{ secrets.OIDC_CLIENT_SECRET }} + loginURL: ${{ secrets.OIDC_ISSUER }}/protocol/openid-connect/auth + id: oidc=aps-portal + loginURLParameters: + - default: + - force + name: approval_prompt + - allow: + - pattern: ".*$" + name: kc_idp_hint + oidcConfig: + audienceClaims: + - aud + emailClaim: sub + groupsClaim: groups + insecureAllowUnverifiedEmail: true + insecureSkipNonce: true + issuerURL: ${{ secrets.OIDC_ISSUER }} + userIDClaim: sub + + profileURL: ${{ secrets.OIDC_ISSUER }}/protocol/openid-connect/userinfo + provider: oidc + redeemURL: ${{ secrets.OIDC_ISSUER }}/protocol/openid-connect/token + scope: openid + validateURL: ${{ secrets.OIDC_ISSUER }}/protocol/openid-connect/userinfo + + server: + BindAddress: 0.0.0.0:7999 + SecureBindAddress: "" + TLS: null + upstreamConfig: + upstreams: + - flushInterval: 1s + id: / + passHostHeader: true + path: / + proxyWebSockets: true + timeout: 30s + uri: http://127.0.0.1:3000 + env: SESSION_SECRET: value: '234873290483290' diff --git a/README.md b/README.md index 62427ec00..b43db75be 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,10 @@ hostip=$(ifconfig en0 | awk '$1 == "inet" {print $2}') docker run -ti --rm --name proxy --net=host \ --add-host portal.localtest.me:$hostip \ + -v `pwd`/local/oauth2-proxy/oauth2-proxy-dev.yaml:/oauth2.yaml \ -v `pwd`/local/oauth2-proxy/oauth2-proxy-dev.cfg:/oauth2.config \ - quay.io/oauth2-proxy/oauth2-proxy:v7.2.0 \ - --config /oauth2.config + quay.io/oauth2-proxy/oauth2-proxy:v7.8.1 \ + --alpha-config /oauth2.yaml --config /oauth2.config ``` 1. Start the Portal locally: diff --git a/docker-compose.yml b/docker-compose.yml index f78364ed4..9fea573e7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,18 +42,17 @@ services: aliases: - keycloak.localtest.me oauth2-proxy: - image: quay.io/oauth2-proxy/oauth2-proxy:v7.2.0 + image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1 container_name: oauth2-proxy - command: --config ./oauth2-proxy.cfg + command: --alpha-config /oauth2-proxy.yaml --config /oauth2-proxy.cfg depends_on: - keycloak ports: - 4180:4180/tcp volumes: + - ./local/oauth2-proxy/oauth2-proxy-local.yaml:/oauth2-proxy.yaml - ./local/oauth2-proxy/oauth2-proxy-local.cfg:/oauth2-proxy.cfg restart: unless-stopped - env_file: - - .env.local networks: aps-net: aliases: diff --git a/local/oauth2-proxy/oauth2-proxy-dev.cfg b/local/oauth2-proxy/oauth2-proxy-dev.cfg index 80b0fa158..649a9f576 100644 --- a/local/oauth2-proxy/oauth2-proxy-dev.cfg +++ b/local/oauth2-proxy/oauth2-proxy-dev.cfg @@ -1,27 +1,10 @@ -http_address="0.0.0.0:4180" +cookie_expire="24h" +cookie_refresh="3m" cookie_secret="abcd1234!@#$$++=" +cookie_secure="false" email_domains="*" -provider="oidc" -insecure_oidc_allow_unverified_email="true" -client_id="aps-portal" -client_secret="8e1a17ed-cb93-4806-ac32-e303d1c86018" -scope="openid" -oidc_issuer_url="http://keycloak.localtest.me:9081/auth/realms/master" -login_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/auth" -redeem_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/token" -validate_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo" redirect_url="http://oauth2proxy.localtest.me:4180/oauth2/callback" -profile_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo" -cookie_secure="false" -cookie_refresh="3m" -cookie_expire="24h" -pass_basic_auth="false" -pass_access_token="true" -set_xauthrequest="true" -skip_jwt_bearer_tokens="false" -set_authorization_header="false" -pass_authorization_header="false" skip_auth_regex="/__coverage__|/login|/health|/public|/docs|/redirect|/_next|/images|/devportal|/manager|/about|/maintenance|/admin/session|/ds/api|/gw/api|/feed/|/signout|^[/]$" -whitelist_domains="keycloak.localtest.me:9081" -upstreams=["http://portal.localtest.me:3000"] +skip_jwt_bearer_tokens="false" skip_provider_button='true' +whitelist_domains="keycloak.localtest.me:9081" \ No newline at end of file diff --git a/local/oauth2-proxy/oauth2-proxy-dev.yaml b/local/oauth2-proxy/oauth2-proxy-dev.yaml new file mode 100644 index 000000000..6aa36fb26 --- /dev/null +++ b/local/oauth2-proxy/oauth2-proxy-dev.yaml @@ -0,0 +1,75 @@ +injectRequestHeaders: + - name: X-Forwarded-Groups + values: + - claim: groups + - name: X-Forwarded-User + values: + - claim: user + - name: X-Forwarded-Email + values: + - claim: email + - name: X-Forwarded-Preferred-Username + values: + - claim: preferred_username + - name: X-Forwarded-Access-Token + values: + - claim: access_token +injectResponseHeaders: + - name: X-Auth-Request-User + values: + - claim: user + - name: X-Auth-Request-Email + values: + - claim: email + - name: X-Auth-Request-Preferred-Username + values: + - claim: preferred_username + - name: X-Auth-Request-Groups + values: + - claim: groups + - name: X-Auth-Request-Access-Token + values: + - claim: access_token +metricsServer: + BindAddress: '' + SecureBindAddress: '' + TLS: null +providers: + - clientID: aps-portal + clientSecret: 8e1a17ed-cb93-4806-ac32-e303d1c86018 + id: oidc=aps-portal + loginURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/auth + loginURLParameters: + - default: + - force + name: approval_prompt + - allow: + - pattern: '.*$' + name: kc_idp_hint + oidcConfig: + audienceClaims: + - aud + emailClaim: email + groupsClaim: groups + insecureAllowUnverifiedEmail: true + insecureSkipNonce: true + issuerURL: http://keycloak.localtest.me:9081/auth/realms/master + userIDClaim: email + profileURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo + provider: oidc + redeemURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/token + scope: openid + validateURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo +server: + BindAddress: 0.0.0.0:4180 + SecureBindAddress: '' + TLS: null +upstreamConfig: + upstreams: + - flushInterval: 1s + id: / + passHostHeader: true + path: / + proxyWebSockets: true + timeout: 30s + uri: http://portal.localtest.me:3000 diff --git a/local/oauth2-proxy/oauth2-proxy-local.cfg b/local/oauth2-proxy/oauth2-proxy-local.cfg index 50edd8bd6..fd9accb8d 100644 --- a/local/oauth2-proxy/oauth2-proxy-local.cfg +++ b/local/oauth2-proxy/oauth2-proxy-local.cfg @@ -1,30 +1,13 @@ -http_address="0.0.0.0:4180" +cookie_expire="24h" +cookie_refresh="3m" cookie_secret="abcd1234!@#$$++=" +cookie_secure="false" email_domains="*" -provider="oidc" -insecure_oidc_allow_unverified_email="true" -client_id="aps-portal" -client_secret="8e1a17ed-cb93-4806-ac32-e303d1c86018" -scope="openid" -oidc_issuer_url="http://keycloak.localtest.me:9081/auth/realms/master" -login_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/auth" -redeem_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/token" -validate_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo" redirect_url="http://oauth2proxy.localtest.me:4180/oauth2/callback" -profile_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo" -cookie_secure="false" -cookie_refresh="3m" -cookie_expire="24h" -pass_basic_auth="false" -pass_access_token="true" -set_xauthrequest="true" -skip_jwt_bearer_tokens="false" -set_authorization_header="false" -pass_authorization_header="false" skip_auth_regex="/__coverage__|/login|/health|/public|/docs|/redirect|/_next|/images|/devportal|/manager|/about|/maintenance|/admin/session|/ds/api|/gw/api|/feed/|/signout|^[/]$" -whitelist_domains="keycloak.localtest.me:9081" -upstreams=["http://apsportal.localtest.me:3000"] +skip_jwt_bearer_tokens="false" skip_provider_button='true' +whitelist_domains="keycloak.localtest.me:9081" redis_connection_url="redis://redis-master:6379" session_store_type='redis' redis_password='s3cr3t' diff --git a/local/oauth2-proxy/oauth2-proxy-local.yaml b/local/oauth2-proxy/oauth2-proxy-local.yaml new file mode 100644 index 000000000..dbdbe240f --- /dev/null +++ b/local/oauth2-proxy/oauth2-proxy-local.yaml @@ -0,0 +1,75 @@ +injectRequestHeaders: + - name: X-Forwarded-Groups + values: + - claim: groups + - name: X-Forwarded-User + values: + - claim: user + - name: X-Forwarded-Email + values: + - claim: email + - name: X-Forwarded-Preferred-Username + values: + - claim: preferred_username + - name: X-Forwarded-Access-Token + values: + - claim: access_token +injectResponseHeaders: + - name: X-Auth-Request-User + values: + - claim: user + - name: X-Auth-Request-Email + values: + - claim: email + - name: X-Auth-Request-Preferred-Username + values: + - claim: preferred_username + - name: X-Auth-Request-Groups + values: + - claim: groups + - name: X-Auth-Request-Access-Token + values: + - claim: access_token +metricsServer: + BindAddress: '' + SecureBindAddress: '' + TLS: null +providers: + - clientID: aps-portal + clientSecret: 8e1a17ed-cb93-4806-ac32-e303d1c86018 + id: oidc=aps-portal + loginURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/auth + loginURLParameters: + - default: + - force + name: approval_prompt + - allow: + - pattern: '.*$' + name: kc_idp_hint + oidcConfig: + audienceClaims: + - aud + emailClaim: email + groupsClaim: groups + insecureAllowUnverifiedEmail: true + insecureSkipNonce: true + issuerURL: http://keycloak.localtest.me:9081/auth/realms/master + userIDClaim: email + profileURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo + provider: oidc + redeemURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/token + scope: openid + validateURL: http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo +server: + BindAddress: 0.0.0.0:4180 + SecureBindAddress: '' + TLS: null +upstreamConfig: + upstreams: + - flushInterval: 1s + id: / + passHostHeader: true + path: / + proxyWebSockets: true + timeout: 30s + uri: http://apsportal.localtest.me:3000 diff --git a/src/controllers/v1/NamespaceController.ts b/src/controllers/v1/NamespaceController.ts index 743edeb8e..d1333a6d6 100644 --- a/src/controllers/v1/NamespaceController.ts +++ b/src/controllers/v1/NamespaceController.ts @@ -14,6 +14,8 @@ import { WorkbookService } from '../../services/report/workbook.service'; import { Namespace } from '@/services/keystone/types'; import { Logger } from '../../logger'; +import { strict as assert } from 'assert'; + import { Readable } from 'stream'; /** @@ -81,6 +83,7 @@ export class NamespaceController extends Controller { query: list, }); logger.debug('Result %j', result); + assert.strictEqual('errors' in result, false, 'Unable to process request'); return result.data.allNamespaces.map((ns: Namespace) => ns.name); } } diff --git a/src/controllers/v2/NamespaceController.ts b/src/controllers/v2/NamespaceController.ts index fef567950..ccae8b5e7 100644 --- a/src/controllers/v2/NamespaceController.ts +++ b/src/controllers/v2/NamespaceController.ts @@ -110,6 +110,7 @@ export class NamespaceController extends Controller { query: list, }); logger.debug('Result %j', result); + assert.strictEqual('errors' in result, false, 'Unable to process request'); return result.data.allNamespaces.map((ns: Namespace) => ns.name).sort(); } diff --git a/src/controllers/v3/GatewayController.ts b/src/controllers/v3/GatewayController.ts index 7e7e70831..279c1ba3e 100644 --- a/src/controllers/v3/GatewayController.ts +++ b/src/controllers/v3/GatewayController.ts @@ -111,11 +111,22 @@ export class NamespaceController extends Controller { query: list, }); logger.debug('Result %j', result); + assert.strictEqual('errors' in result, false, 'Unable to process request'); + return result.data.allNamespaces - .map((ns: Namespace): Gateway => ({ gatewayId: ns.name, displayName: ns.displayName })) + .map( + (ns: Namespace): Gateway => ({ + gatewayId: ns.name, + displayName: ns.displayName, + }) + ) .sort((a: Gateway, b: Gateway) => { - const displayNameComparison = a.displayName.localeCompare(b.displayName); - return displayNameComparison !== 0 ? displayNameComparison : a.gatewayId.localeCompare(b.gatewayId); + const displayNameComparison = a.displayName.localeCompare( + b.displayName + ); + return displayNameComparison !== 0 + ? displayNameComparison + : a.gatewayId.localeCompare(b.gatewayId); }); } diff --git a/src/services/checkStatus.ts b/src/services/checkStatus.ts index f83b44500..881ebb4a6 100644 --- a/src/services/checkStatus.ts +++ b/src/services/checkStatus.ts @@ -13,6 +13,7 @@ export async function checkStatus(res: any) { reason: 'unknown_error', description: '', status: `${res.status} ${res.statusText}`, + statusCode: res.status, }; logger.error('Error - %d %s', res.status, res.statusText); const body = await res.text(); diff --git a/src/services/issuerMisconfigError.ts b/src/services/issuerMisconfigError.ts index 68510d3df..a18f47458 100644 --- a/src/services/issuerMisconfigError.ts +++ b/src/services/issuerMisconfigError.ts @@ -2,6 +2,7 @@ export interface IssuerMisconfigDetail { reason: string; description: string; status: string; + statusCode: number; } export class IssuerMisconfigError extends Error { diff --git a/src/services/uma2/token-service.ts b/src/services/uma2/token-service.ts index 97030bd5c..492d7fb1e 100644 --- a/src/services/uma2/token-service.ts +++ b/src/services/uma2/token-service.ts @@ -97,7 +97,9 @@ export class UMA2TokenService { return res.json(); } else if (res.status == 403) { // users that have no resources will get a 403, so gracefully handle that as "access to no resources" - return []; + return new Promise((resolve) => { + resolve([]); + }); } else { return checkStatus(res); }