Skip to content

Commit

Permalink
Merge branch 'dev' into feature/quarkus
Browse files Browse the repository at this point in the history
  • Loading branch information
NithinKuruba committed Jul 3, 2024
2 parents b769a48 + 42fc007 commit cd426e2
Show file tree
Hide file tree
Showing 20 changed files with 405 additions and 149 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/publish-kc-cron-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Upgrade kc-cron-job dev to production

on:
workflow_dispatch:

env:
GITHUB_REGISTRY: ghcr.io
IMAGE_NAME: bcgov/kc-cron-job

jobs:
build-and-push-image:
runs-on: ubuntu-20.04
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to the GitHub Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Retag and push image
run: |
docker pull ghcr.io/bcgov/kc-cron-job:dev
docker tag ghcr.io/bcgov/kc-cron-job:dev ghcr.io/bcgov/kc-cron-job:prod
docker push ghcr.io/bcgov/kc-cron-job:prod
2 changes: 1 addition & 1 deletion .github/workflows/siteminder-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ jobs:
url: ${{ secrets.SSO_ALERTS }}
method: 'POST'
customHeaders: '{"Content-Type": "application/json"}'
data: '{"text": "Siteminder Tests Failed! @jsharman @jlanglois @Marco @zorin.samji @nithinshekar.kuruba", "attachments": [{"color": "#FF0000","author_name": "${{ github.actor }}", "title": "Failed test", "title_link": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", "text": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", "fields": [{"title": "Environment", "value": "${{ github.event.inputs.environment }}", "short": false}, {"title": "Cluster", "value": "${{ github.event.inputs.cluster }}", "short": false}]}]}'
data: '{"text": "Siteminder Tests Failed! @jsharman @jlanglois @Marco @nithinshekar.kuruba", "attachments": [{"color": "#FF0000","author_name": "${{ github.actor }}", "title": "Failed test", "title_link": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", "text": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", "fields": [{"title": "Environment", "value": "${{ github.event.inputs.environment }}", "short": false}, {"title": "Cluster", "value": "${{ github.event.inputs.cluster }}", "short": false}]}]}'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ mds.json
# mkdocs
__pycache__
site
venv
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ repository for keycloak images and helm chart
## Devhub Docs

Devhub docs are generated using [Techdocs](https://backstage.io/docs/features/techdocs/cli/), a cli tool. To run locally:
- `python -m venv venv`
- `pip install -r requirements.txt`
- `npx @techdocs/cli serve --verbose --no-docker`
29 changes: 18 additions & 11 deletions docker/kc-cron-job/.env.example
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
BCEID_SERVICE_BASIC_AUTH=
BCEID_REQUESTER_IDIR_GUID=
BCEID_SERVICE_ID_DEV=
BCEID_SERVICE_ID_TEST=
BCEID_SERVICE_ID_PROD=
DEV_KEYCLOAK_CLIENT_ID=
DEV_KEYCLOAK_CLIENT_SECRET=
DEV_KEYCLOAK_PASSWORD=
DEV_KEYCLOAK_USERNAME=
DEV_KEYCLOAK_URL=https://dev.loginproxy.gov.bc.ca
TEST_KEYCLOAK_CLIENT_ID=
TEST_KEYCLOAK_CLIENT_SECRET=
TEST_KEYCLOAK_PASSWORD=
TEST_KEYCLOAK_USERNAME=
TEST_KEYCLOAK_URL=https://test.loginproxy.gov.bc.ca
PROD_KEYCLOAK_CLIENT_ID=
PROD_KEYCLOAK_CLIENT_SECRET=
PROD_KEYCLOAK_PASSWORD=
PROD_KEYCLOAK_USERNAME=
PROD_KEYCLOAK_URL=https://loginproxy.gov.bc.ca
PGHOST=
PGPORT=
Expand All @@ -23,3 +18,15 @@ RC_WEBHOOK=
VC_USERS_RETENTION_DAYS=
INACTIVE_IDIR_USERS_RETENTION_DAYS=
NAMESPACE=

MS_GRAPH_API_AUTHORITY_DEV=
MS_GRAPH_API_CLIENT_ID_DEV=
MS_GRAPH_API_CLIENT_SECRET_DEV=

MS_GRAPH_API_AUTHORITY_TEST=
MS_GRAPH_API_CLIENT_ID_TEST=
MS_GRAPH_API_CLIENT_SECRET_TEST=

MS_GRAPH_API_AUTHORITY_PROD=
MS_GRAPH_API_CLIENT_ID_PROD=
MS_GRAPH_API_CLIENT_SECRET_PROD=
2 changes: 2 additions & 0 deletions docker/kc-cron-job/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"test": "jest"
},
"dependencies": {
"@azure/msal-node": "^2.9.2",
"archiver": "^5.3.0",
"async": "^3.2.4",
"axios": "^1.4.0",
"easy-soap-request": "^5.3.0",
"jsonwebtoken": "^9.0.2",
"jws": "^4.0.0",
"keycloak-admin": "^1.14.22",
"lodash": "^4.17.21",
Expand Down
198 changes: 94 additions & 104 deletions docker/kc-cron-job/remove-inactive-idir-users.js
Original file line number Diff line number Diff line change
@@ -1,126 +1,112 @@
const _ = require('lodash');
const { promisify } = require('util');
const { parseString } = require('xml2js');
const async = require('async');
const axios = require('axios');
const { getAdminClient, log, getPgClient, sendRcNotification, handleError, deleteLegacyData } = require('./helpers');
const jwt = require('jsonwebtoken');
const { ConfidentialClientApplication } = require('@azure/msal-node');

require('dotenv').config();

const parseStringSync = promisify(parseString);

function getWebServiceInfo({ env = 'dev' }) {
const requestHeaders = {
'Content-Type': 'text/xml;charset=UTF-8',
authorization: `Basic ${process.env.BCEID_SERVICE_BASIC_AUTH}`
};
const MS_GRAPH_URL = 'https://graph.microsoft.com';
const MS_GRAPH_IDIR_GUID_ATTRIBUTE = 'onPremisesExtensionAttributes/extensionAttribute12';

const requesterIdirGuid = process.env.BCEID_REQUESTER_IDIR_GUID || '';
require('dotenv').config();

let serviceUrl = '';
let serviceId = '';
if (env === 'dev') {
serviceUrl = 'https://gws2.development.bceid.ca';
serviceId = process.env.BCEID_SERVICE_ID_DEV || '';
} else if (env === 'test') {
serviceUrl = 'https://gws2.test.bceid.ca';
serviceId = process.env.BCEID_SERVICE_ID_TEST || '';
} else if (env === 'prod') {
serviceUrl = 'https://gws2.bceid.ca';
serviceId = process.env.BCEID_SERVICE_ID_PROD || '';
}
let devMsalInstance;
let testMsalInstance;
let prodMsalInstance;

return { requestHeaders, requesterIdirGuid, serviceUrl, serviceId };
}

const generateXML = (
{
property = 'userId',
matchKey = '',
matchType = 'Exact',
serviceId = '',
requesterIdirGuid = '',
page = 1,
limit = 1
let msTokenCache = {
dev: {
token: '',
decoded: null
},
test: {
token: '',
decoded: null
},
requestType = 'searchInternalAccount'
) => {
if (requestType === 'getAccountDetail') {
return `<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:V10="http://www.bceid.ca/webservices/Client/V10/">
<soapenv:Header />
<soapenv:Body>
<V10:getAccountDetail>
<V10:accountDetailRequest>
<V10:onlineServiceId>${serviceId}</V10:onlineServiceId>
<V10:requesterAccountTypeCode>Internal</V10:requesterAccountTypeCode>
<V10:requesterUserGuid>${requesterIdirGuid}</V10:requesterUserGuid>
<V10:${property}>${matchKey}</V10:${property}>
<V10:accountTypeCode>Internal</V10:accountTypeCode>
</V10:accountDetailRequest>
</V10:getAccountDetail>
</soapenv:Body>
</soapenv:Envelope>`;
} else {
return `<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:V10="http://www.bceid.ca/webservices/Client/V10/">
<soapenv:Header />
<soapenv:Body>
<V10:searchInternalAccount>
<V10:internalAccountSearchRequest>
<V10:onlineServiceId>${serviceId}</V10:onlineServiceId>
<V10:requesterAccountTypeCode>Internal</V10:requesterAccountTypeCode>
<V10:requesterUserGuid>${requesterIdirGuid}</V10:requesterUserGuid>
<requesterAccountTypeCode>Internal</requesterAccountTypeCode>
<V10:pagination>
<V10:pageSizeMaximum>${String(limit || 100)}</V10:pageSizeMaximum>
<V10:pageIndex>${String(page || 1)}</V10:pageIndex>
</V10:pagination>
<V10:sort>
<V10:direction>Ascending</V10:direction>
<V10:onProperty>UserId</V10:onProperty>
</V10:sort>
<V10:accountMatch>
<V10:${property}>
<V10:value>${matchKey}</V10:value>
<V10:matchPropertyUsing>${matchType}</V10:matchPropertyUsing>
</V10:${property}>
</V10:accountMatch>
</V10:internalAccountSearchRequest>
</V10:searchInternalAccount>
</soapenv:Body>
</soapenv:Envelope>`;
prod: {
token: '',
decoded: null
}
};

async function checkUserExistsAtIDIM({ property = 'userGuid', matchKey = '', env = 'prod' }) {
const { requestHeaders, requesterIdirGuid, serviceUrl, serviceId } = getWebServiceInfo({ env });
const xml = generateXML({ property, matchKey, serviceId, requesterIdirGuid }, 'getAccountDetail');

async function getAzureAccessToken(env) {
try {
const response = await axios.post(`${serviceUrl}/webservices/client/V10/BCeIDService.asmx?WSDL`, xml, {
headers: requestHeaders,
timeout: 10000
});
const currentTime = Math.floor(Date.now() / 1000);
if (msTokenCache[env].decoded && msTokenCache[env].decoded?.exp > currentTime) {
return msTokenCache[env].token;
}
const request = {
scopes: [`${MS_GRAPH_URL}/.default`]
};

const { data: body } = response;
let msalInstance;
switch (env) {
case 'dev':
msalInstance =
devMsalInstance ||
new ConfidentialClientApplication({
auth: {
authority: process.env.MS_GRAPH_API_AUTHORITY_DEV || '',
clientId: process.env.MS_GRAPH_API_CLIENT_ID_DEV || '',
clientSecret: process.env.MS_GRAPH_API_CLIENT_SECRET_DEV || ''
}
});
break;
case 'test':
msalInstance =
testMsalInstance ||
new ConfidentialClientApplication({
auth: {
authority: process.env.MS_GRAPH_API_AUTHORITY_TEST || '',
clientId: process.env.MS_GRAPH_API_CLIENT_ID_TEST || '',
clientSecret: process.env.MS_GRAPH_API_CLIENT_SECRET_TEST || ''
}
});
break;
case 'prod':
msalInstance =
prodMsalInstance ||
new ConfidentialClientApplication({
auth: {
authority: process.env.MS_GRAPH_API_AUTHORITY_PROD || '',
clientId: process.env.MS_GRAPH_API_CLIENT_ID_PROD || '',
clientSecret: process.env.MS_GRAPH_API_CLIENT_SECRET_PROD || ''
}
});
break;
}
const response = await msalInstance.acquireTokenByClientCredential(request);
msTokenCache[env].token = response.accessToken;
msTokenCache[env].decoded = jwt.decode(response.accessToken);
return response.accessToken;
} catch (error) {
console.error(error);
throw new Error('Error acquiring access token');
}
}

const result = await parseStringSync(body);
const data = _.get(result, 'soap:Envelope.soap:Body.0.getAccountDetailResponse.0.getAccountDetailResult.0');
if (!data) throw Error('no data');
async function checkUserExistsAtIDIM({ property = MS_GRAPH_IDIR_GUID_ATTRIBUTE, matchKey = '', env }) {
try {
const accessToken = await getAzureAccessToken(env);
const options = {
headers: {
Authorization: `Bearer ${accessToken}`,
ConsistencyLevel: 'eventual'
}
};

const status = _.get(data, 'code.0');
const failureCode = _.get(data, 'failureCode.0');
const failMessage = _.get(data, 'message.0');
if (status === 'Success' && failureCode === 'Void') {
return 'exists';
} else if (status === 'Failed' && failureCode === 'NoResults') {
const url = `${MS_GRAPH_URL}/v1.0/users?$filter=${property} eq '${matchKey}'&$count=true`;
const result = await axios.get(url, options);
if (result && result.data?.value?.length === 0) {
return 'notexists';
} else {
log(`${env}: [${status}][${failureCode}] ${property}: ${matchKey}: ${String(failMessage)})`);
}
if (result && result.data?.value?.length > 0) {
return 'exists';
}
console.error(`unexpected response from ms graph: ${result}`);
return 'error';
} catch (error) {
console.log(error?.response?.data || error);
throw new Error(error);
}
}
Expand Down Expand Up @@ -195,7 +181,11 @@ async function removeStaleUsersByEnv(env = 'dev', pgClient, runnerName, startFro
if (displayName && displayName.startsWith('hold -')) continue;
log(`[${runnerName}] processing user ${username}`);
if (username.includes('@idir')) {
const userExistsAtWb = await checkUserExistsAtIDIM({ property: 'userGuid', matchKey: idirUserGuid, env });
const userExistsAtWb = await checkUserExistsAtIDIM({
property: MS_GRAPH_IDIR_GUID_ATTRIBUTE,
matchKey: idirUserGuid,
env
});
if (userExistsAtWb === 'notexists') {
const { realmRoles, clientRoles } = await getUserRolesMappings(adminClient, id);
await removeUserFromKc(adminClient, id);
Expand Down
Loading

0 comments on commit cd426e2

Please sign in to comment.