Common Hosted Single Sign-On (CSS) is an self-serve application developed by Pathfinder SSO Team to enable digital product development teams to request integrations with BC Government approved login options such as IDIR, BCeID and more.
- Github: The CSS UI is a static site deployed as GitHub pages through CI/CD defined using GitHub actions. Refer to /app subdirectory for more details.
- AWS: CSS backend is built upon AWS lambda functions protected by AWS API gateway, and supported by AWS RDS postgres database.
- Terraform: The infrastructure for CSS backend is created and version controlled through terraform. The backed is deployed into an existing AWS zone, and uses the predefined VPC and subnets. The terraform code is located under /terraform subdirectory.
- OCP: OpenShift environment offered through private cloud. All the keycloak instances are located in environment specific namespaces (xxxx-dev, xxxx-test, and xxxx-prod)
The general workflow for new client creation is:
- W1: A user authenticates with their IDIR and fills in a form for their new client/integration. The form submission sends a request to CSS backend through AWS API gateway
- W2: The lambda function (see
lambda/app
) evaluates the data and after successful validation, the integration is added to a queue table in AWS RDS. If this process fails then the integration status is updated toplanFailed
- W3: The integration is picked from queue and is then used to build an openid or saml client request w/ payload depending upon the protocol and sent to keycloak via admin API for creating a new client. After successful application, the integration is deleted from queue and its status is updated to
applied
. If the client creation fails at keycloak, then the integration remains in queue table and its status is updated toapplyFailed
. - W4: The scheduler lambda function runs every 5 minutes and it processes all the integrations found in queue table through
lambda/app
. The successful integrations are removed from queue and updated withapplied
status. - W5: The
data-integrity
lambda function runs daily in the morning. It scans the CSS database for active integrations and lists out any missing clients from Keycloak and notifies the Pathfinder SSO team viaRocket.Chat
. - IAC: The CSS repo folder
./terraform
holds the terraform code to deploy the CSS backend and the grafana instances in AWS environment.
The front end application is built with nextjs (a react framework). It is a static application hosted on github pages. A static single-page-application was chosen to deliver a fast user experience, take advantage of pre-built bcgov components, and to interact as an independant layer from the backend.
The backend uses AWS serverless technologies, which was chosen to reduce cost (with serverless, you only pay for what you use) since this application is expected to have low traffic. It uses:
- Lambda: Lambda functions are used to handle backend logic on API events.
- API Gateway: API Gateway supports serverless infrastructure, and is used to proxy API events to the lambda functions.
- RDS: An amazon relational database that supports serverless infrastructure.
- S3: Holds the swagger static site that can be accessed at here and also used for storing the CSS backend terraform state
- DynamoDB: Used to store the terraform lock to streamline multiple deployments to AWS
- CloudWatch: Stores all the logs generated by lambda functions
- Events: Schedulers that trigger some Github actions and lambda functions
Only users with IDIR with or without MFA are allowed to login and request for an integration.
- The user sigining into the application can request an integration for self or, could create a team and request for a team integration.
- The user can add team members to his team and grant them either
admin
ormember
role - The pathfinder team members have the super-admin permissions, where they have access to all the requested integrations and teams.
For getting details on IDIR users (e.g valid user lookups, importing into keycloak) we are using Microsoft Graph. For details on implementation see here.
To setup a local development environment, a detailed guide may be found here.
This repository requires the following secrets:
-
API_AUTH_SECRET
: The basic secret for endpoint to remove inactive IDIR users from CSS -
AWS_ECR_URI
: AWS container registry URI -
CHES_USERNAME
: Username of CHES account -
CHES_PASSWORD
: Password of CHES account -
KEYCLOAK_V2_DEV_URL
: The URL of the keycloak dev environment. -
KEYCLOAK_V2_TEST_URL
: The URL of the keycloak test environment. -
KEYCLOAK_V2_PROD_URL
: The URL of the keycloak prod environment. -
KEYCLOAK_V2_DEV_USERNAME
: The client id for the keycloak dev environment. -
KEYCLOAK_V2_TEST_USERNAME
: The client id for the keycloak test environment. -
KEYCLOAK_V2_PROD_USERNAME
: The client id for the keycloak prod environment. -
KEYCLOAK_V2_DEV_PASSWORD
: The client secret for the keycloaks dev environment. -
KEYCLOAK_V2_TEST_PASSWORD
: The client secret for the keycloaks test environment. -
KEYCLOAK_V2_PROD_PASSWORD
: The client secret for the keycloaks prod environment. -
GH_ACCESS_TOKEN
: Access token for the github service account (used to trigger workflows insso-terraform
). -
GH_SECRET
: The secret used to authorize requests between github actions (fromsso-terraform
) to the API. -
TFC_TEAM_TOKEN
: The token used to run terraform cloud. -
GRAFANA_API_TOKEN
: The API token to authenticate with Grafana and pull metrics and logs data. -
TERRAFORM_DEPLOY_ROLE_ARN
: The AWS role arn required by AWS SDK in terraform github workflow to authenticate with AWS and update resources. In Addition, the following secrets are required forsso-terraform
andsso-terraform-dev
to work with this app: -
GH_ACCESS_TOKEN
: Access token for the github service account (used to run github API calls). -
GH_SECRET
: The secret used to authorize requests between github actions (fromsso-terraform
) to the API. -
REALM_REGISTRY_API
: The API URL of Realm Registry -
VERIFY_USER_SECRET
: A secret or a private key used for signing team invitation tokens
To run this app, you will need to setup your terraform infrastructure. The code in the terraform directory
assumes one available VPC exists with available subnets in two zones. To set the names for your subnets the terraform variables
subnet_a
and subnet_b
should be set.
The lambda functions are written in typescript, and to compile and bundle them you can run make lambda-all
from the lambda directory.
Once they are compiled, from the terraform directory you can run terraform apply
to build the backend.
This repository has frontend tests using jest and react testing library, and backend tests run with jest.
Tests are run in CI in the test.yml
file.
To run the frontend tests, from the app
directory run yarn test
. If adding a snapshot test,
running yarn test
will add the new snapshot file for you. If you want to update a snapshot test,
run yarn test -u
To run the backend unit tests,
-
you will need to have a local postgres database running. Ensure you have started the server with
pg_ctl start
-
For the first time running the tests, the database will need to be created. Run
make local_db
from the root directory Note: you may need to runchmod +x ./.bin/db-setup.sh
to give necessary permissions. -
Run
make server_test
to run all the backend unit tests -
Navigate to
./lambda/jest_stare
directory and open theindex.html
to view the report that shows the code coverage
Error codes we use for the application:
E01
: There is an application in thesso-terraform
repository that cannot be applied, blocking new requestsE02
: The user has a token in their session storage that is invalidE03
: Missing of invalid email address associated with your IDIR accountE04
: Request timeoutE05
: 503 Service Temporarily Unavailable
To use, you will need access to the AWS platform as well as the database credentials.
- In the AWS console, navigate to the
RDS
dashboard and select DB Clusters from the resources panel. - Select
aurora-db-postgres
. From the top rightactions
dropdown select query. - You will be prompted for the db host, db name, username and password. Enter these and select
connect
(it may take some time to connect) - From the saved queries, select the one you would like to use (refer to descriptions for information)
There are two instances of Grafana, one for sandbox and the other for production environments. Both of them are deployed in AWS ECS containers with persistent storage through AWS EBS under environment specific VPCs. Grafana has read only access to the database.
Some queries are id specific, update the where clause to change the request number_
-- Count of clients in draft
select count(*) from requests where status='draft';
-- Count of clients awaiting approval (note: currently auto-approve but will apply when bceid is added)
select count(*) from requests where status='submitted';
-- Count of clients completed
select count(*) from requests where status='applied';
-- (initial )time request was fulfilled (dev, test, and prod)
select events.created_at from events join requests on requests.id = events.request_id where requests.id=1 and events.event_code = 'request-apply-success';
-- get all clients. note the null are those that have not submittted request (zs)
select client_name, preferred_email from requests where archived = false;
Queries to fetch user emails
-- Integration users with a team associated with
SELECT
r.id,
r.client_name,
r.service_type,
r.team_id,
ut.user_id,
u.idir_email,
u.additional_email
FROM
requests as r
INNER JOIN users_teams as ut ON r.team_id=ut.team_id
INNER JOIN users as u ON u.id=ut.user_id
WHERE r.uses_team=TRUE
AND r.archived=FALSE
AND ut.pending=FALSE
ORDER BY r.client_name
-- Integration users with no team associated with
SELECT
r.id,
r.client_name,
r.user_id,
r.service_type,
u.idir_email,
u.additional_email
FROM
requests as r
INNER JOIN users as u ON u.id=r.user_id
WHERE r.uses_team=FALSE
AND r.archived=FALSE
ORDER BY r.client_name
- Create a pull request from
dev
tomain
and update pull request labels to choose a specific type of release release:major
- will create a major release (example:v1.0.0
->v2.0.0
)release:minor
- will create a minor release (example:v1.0.0
->v1.1.0
)release:patch
- will create a patch release (example:v1.0.0
->v1.0.1
)release:norelease
- will not trigger any release
- Login to AWS and create a new private repository in AWS Elastic Container Registry
export AWS_ECR_URI=
aws ecr get-login-password --region ca-central-1 | docker login --username AWS --password-stdin $AWS_ECR_URI
docker pull --platform linux/amd64 grafana/grafana:9.3.2
docker tag grafana/grafana:9.3.2 $AWS_ECR_URI/bcgov-sso/grafana:9.3.2
docker push $AWS_ECR_URI/bcgov-sso/grafana:9.3.2
- Create a new secret
export GF_SECURITY_ADMIN_PASSWORD=
# login to CSS app and download the prod installation json for the SSO Dashboard integration
export GF_AUTH_GENERIC_OAUTH_CLIENT_ID=
export GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=
aws secretsmanager create-secret --name sso-grafana --description "SSO Grafana Secrets" \
--secret-string '{"GF_SECURITY_ADMIN_PASSWORD": "$GF_SECURITY_ADMIN_PASSWORD", "GF_AUTH_GENERIC_OAUTH_CLIENT_ID": "$GF_AUTH_GENERIC_OAUTH_CLIENT_ID", "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": "$GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET"}' --region ca-central-1
- Create a policy
aws iam create-policy --policy-name SSOPathfinderReadGrafanaSecretInfo --policy-document ./policy.json
# navigate to secrets manager and copy the secret ARN
export GRAFANA_SECRET_ARN=
# policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "$GRAFANA_SECRET_ARN"
}
]
}
CREATE USER cssgrafana WITH PASSWORD '<secure-password>';
GRANT CONNECT ON DATABASE ssorequests TO cssgrafana;
GRANT USAGE ON SCHEMA public TO cssgrafana;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO cssgrafana;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO cssgrafana;
SELECT * from pg_catalog.pg_roles;
SELECT * FROM pg_catalog.pg_auth_members;
# check privileges on a table
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_name='requests'