diff --git a/NOTICE b/NOTICE index 923eb62c8ac2fa47e1c4d123ba97d27391b428a1..f536768769dc47011231650779b56ca6e792a692 100644 --- a/NOTICE +++ b/NOTICE @@ -7,7 +7,7 @@ AFL-3.0 ======================================================================== The following software have components provided under the terms of this license: -- json-schema (from https://www.npmjs.com/package/json-schema) +- json-schema (from ) ======================================================================== Apache-2.0 @@ -137,7 +137,7 @@ The following software have components provided under the terms of this license: - rc (from https://www.npmjs.com/package/rc) - shimmer (from https://www.npmjs.com/package/shimmer) - source-map (from https://www.npmjs.com/package/source-map) -- sprintf-js (from https://www.npmjs.com/package/sprintf-js) +- sprintf-js (from ) - tough-cookie (from https://www.npmjs.com/package/tough-cookie) - tough-cookie (from https://www.npmjs.com/package/tough-cookie) - tslib (from https://www.npmjs.com/package/tslib) @@ -174,7 +174,7 @@ The following software have components provided under the terms of this license: - google-gax (from https://www.npmjs.com/package/google-gax) - idna (from https://github.com/kjd/idna) - ieee754 (from https://www.npmjs.com/package/ieee754) -- json-schema (from https://www.npmjs.com/package/json-schema) +- json-schema (from ) - node-forge (from https://www.npmjs.com/package/node-forge) - node-forge (from https://www.npmjs.com/package/node-forge) - node-pre-gyp (from https://www.npmjs.com/package/node-pre-gyp) @@ -187,7 +187,7 @@ The following software have components provided under the terms of this license: - qs (from https://www.npmjs.com/package/qs) - rc (from https://www.npmjs.com/package/rc) - source-map (from https://www.npmjs.com/package/source-map) -- sprintf-js (from https://www.npmjs.com/package/sprintf-js) +- sprintf-js (from ) - stack-chain (from https://www.npmjs.com/package/stack-chain) - tough-cookie (from https://www.npmjs.com/package/tough-cookie) - tough-cookie (from https://www.npmjs.com/package/tough-cookie) @@ -306,7 +306,7 @@ The following software have components provided under the terms of this license: - which (from https://www.npmjs.com/package/which) - which-module (from https://www.npmjs.com/package/which-module) - wide-align (from https://www.npmjs.com/package/wide-align) -- wrappy (from https://github.com/npm/wrappy) +- wrappy (from https://www.npmjs.com/package/wrappy) - write-file-atomic (from https://www.npmjs.com/package/write-file-atomic) - y18n (from https://www.npmjs.com/package/y18n) - y18n (from https://www.npmjs.com/package/y18n) @@ -322,7 +322,7 @@ LGPL-2.1-only The following software have components provided under the terms of this license: - chardet (from https://github.com/chardet/chardet) -- xmlrunner (from https://github.com/pycontribs/xmlrunner) +- xmlrunner (from ) ======================================================================== LGPL-2.1-or-later @@ -433,7 +433,7 @@ The following software have components provided under the terms of this license: - call-bind (from https://www.npmjs.com/package/call-bind) - camelcase (from https://www.npmjs.com/package/camelcase) - camelize (from https://www.npmjs.com/package/camelize) -- cffi (from http://cffi.readthedocs.org) +- cffi (from ) - chalk (from https://www.npmjs.com/package/chalk) - chownr (from https://www.npmjs.com/package/chownr) - cliui (from https://www.npmjs.com/package/cliui) @@ -479,7 +479,7 @@ The following software have components provided under the terms of this license: - debuglog (from https://www.npmjs.com/package/debuglog) - decamelize (from https://www.npmjs.com/package/decamelize) - decompress-response (from https://www.npmjs.com/package/decompress-response) -- deep-extend (from https://www.npmjs.com/package/deep-extend) +- deep-extend (from ) - define-properties (from https://www.npmjs.com/package/define-properties) - delayed-stream (from https://www.npmjs.com/package/delayed-stream) - delegates (from https://www.npmjs.com/package/delegates) @@ -522,7 +522,7 @@ The following software have components provided under the terms of this license: - fn.name (from https://www.npmjs.com/package/fn.name) - follow-redirects (from https://www.npmjs.com/package/follow-redirects) - follow-redirects (from https://www.npmjs.com/package/follow-redirects) -- for-each (from https://www.npmjs.com/package/for-each) +- for-each (from ) - form-data (from https://www.npmjs.com/package/form-data) - form-data (from https://www.npmjs.com/package/form-data) - form-data (from https://www.npmjs.com/package/form-data) @@ -600,12 +600,12 @@ The following software have components provided under the terms of this license: - isarray (from https://www.npmjs.com/package/isarray) - isexe (from https://www.npmjs.com/package/isexe) - isstream (from https://github.com/rvagg/isstream) -- jmespath (from https://www.npmjs.com/package/jmespath) - jmespath (from https://github.com/jmespath/jmespath.py) +- jmespath (from https://www.npmjs.com/package/jmespath) - jsbn (from https://www.npmjs.com/package/jsbn) - json-bigint (from https://www.npmjs.com/package/json-bigint) - json-bigint (from https://www.npmjs.com/package/json-bigint) -- json-schema (from https://www.npmjs.com/package/json-schema) +- json-schema (from ) - json-schema-traverse (from https://www.npmjs.com/package/json-schema-traverse) - json-stringify-safe (from https://www.npmjs.com/package/json-stringify-safe) - jsonfile (from https://www.npmjs.com/package/jsonfile) @@ -855,7 +855,7 @@ The following software have components provided under the terms of this license: - winston-transport (from https://www.npmjs.com/package/winston-transport) - wrap-ansi (from https://www.npmjs.com/package/wrap-ansi) - wrap-ansi (from https://www.npmjs.com/package/wrap-ansi) -- wrappy (from https://github.com/npm/wrappy) +- wrappy (from https://www.npmjs.com/package/wrappy) - write-file-atomic (from https://www.npmjs.com/package/write-file-atomic) - xdg-basedir (from https://www.npmjs.com/package/xdg-basedir) - xml2js (from https://www.npmjs.com/package/xml2js) diff --git a/devops/azure/chart/templates/azure-istio-auth-policy.yaml b/devops/azure/chart/templates/azure-istio-auth-policy.yaml index 306e8b2ef103a4ab2a11043a96917ec5ab5be2b6..8acbd7be039c0836200ffbe5ecb8baf80d8aa7e3 100644 --- a/devops/azure/chart/templates/azure-istio-auth-policy.yaml +++ b/devops/azure/chart/templates/azure-istio-auth-policy.yaml @@ -14,8 +14,8 @@ spec: notRequestPrincipals: ["*"] to: - operation: - notPaths: ["/","*/index.html", - "*/v2/api-docs", - "*/actuator/health", "*/health", - "*/configuration/ui","*/configuration/security", - "/seistore-svc/api/v3/swagger-ui.html*"] + notPaths: ["/", + "*/swagger-ui.html*", + "*/svcstatus", + "*/svcstatus/readiness" + ] diff --git a/devops/azure/chart/templates/deployment.yaml b/devops/azure/chart/templates/deployment.yaml index 9a5ac488e9ced4396ba9ee700c503a28f46518a5..a7c1444d02d41988c833170618af8d373eff6db1 100644 --- a/devops/azure/chart/templates/deployment.yaml +++ b/devops/azure/chart/templates/deployment.yaml @@ -35,7 +35,7 @@ spec: periodSeconds: 60 readinessProbe: httpGet: - path: /seistore-svc/api/v3/svcstatus + path: /seistore-svc/api/v3/svcstatus/readiness port: 80 httpHeaders: - name: X-Api-Key diff --git a/src/cloud/providers/azure/credentials.ts b/src/cloud/providers/azure/credentials.ts index 140c9c7b30c5debfe8f279d2341f07e1a2590be9..de6c180798865a7e785102b3f40c0094c211eea6 100644 --- a/src/cloud/providers/azure/credentials.ts +++ b/src/cloud/providers/azure/credentials.ts @@ -23,8 +23,9 @@ import { SASProtocol, UserDelegationKey } from '@azure/storage-blob'; -import { AzureConfig } from './config'; -import { DefaultAzureCredential, TokenCredential } from '@azure/identity'; +import { DefaultAzureCredential, TokenCredential, DefaultAzureCredentialOptions} from '@azure/identity'; +import {ExponentialRetryPolicyOptions} from '@azure/core-rest-pipeline' +import { AccessToken, GetTokenOptions } from '@azure/core-auth'; import request from 'request-promise'; import { AzureDataEcosystemServices } from './dataecosystem'; @@ -69,7 +70,7 @@ export class AzureCredentials extends AbstractCredentials { // - AZURE_CLIENT_SECRET: The client secret for the registered application // https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview - return new DefaultAzureCredential(); + return new RetriableAzureCredential(); } @@ -194,3 +195,48 @@ export class AzureCredentials extends AbstractCredentials { } } + + +export class RetriableAzureCredential extends DefaultAzureCredential { + private static DefaultRetryCount = 10; + private static DefaultRetryInterval = 1000; + private static DefaultMaxRetryInterval = 64 * 1000; + private static DefaultRequestTimeout = 5 * 1000; + + private options:ExponentialRetryPolicyOptions = { + maxRetries: RetriableAzureCredential.DefaultRetryCount, + retryDelayInMs: RetriableAzureCredential.DefaultRetryInterval, // Not supported yet + maxRetryDelayInMs: RetriableAzureCredential.DefaultMaxRetryInterval // Not supported yet + }; + + private credentials: DefaultAzureCredential; + + private defaultRequestOptions = { + requestOptions: { + timeout: RetriableAzureCredential.DefaultRequestTimeout + } + }; + + public constructor(tokenCredentialOptions?: DefaultAzureCredentialOptions) { + super(tokenCredentialOptions); + const retryOptions = tokenCredentialOptions?.retryOptions; + this.options = {...this.options, ...retryOptions} + this.credentials = new DefaultAzureCredential(); + } + + public getToken(scopes: string | string[], options?: GetTokenOptions): Promise { + const requestOptions = {...options, ...this.defaultRequestOptions}; + return this.retry(() => this.credentials.getToken(scopes, requestOptions)); + } + + private async retry (fn: () => Promise, retries: number = this.options.maxRetries): Promise { + if(retries <= 0) { + return Promise.reject('Failed after several attempts'); + } + + const prom = fn(); + return prom + .then(res => prom) + .catch(err => this.retry(fn, retries - 1)) + } +} \ No newline at end of file diff --git a/src/cloud/providers/azure/keyvault.ts b/src/cloud/providers/azure/keyvault.ts index d9519b5d189dcbcaa5d09556403444f97a452b0a..d844fdb9e588807597fd6e2c5e5f1449103c6e5d 100644 --- a/src/cloud/providers/azure/keyvault.ts +++ b/src/cloud/providers/azure/keyvault.ts @@ -74,7 +74,7 @@ export class Keyvault { AzureConfig.SP_CLIENT_SECRET = AzureConfig.SP_CLIENT_SECRET = (await client.getSecret(this.SP_CLIENT_SECRET)).value; AzureConfig.SP_APP_RESOURCE_ID = - AzureConfig.SP_APP_RESOURCE_ID = (await client.getSecret(this.SP_APP_RESOURCE_ID)).value;; + AzureConfig.SP_APP_RESOURCE_ID = (await client.getSecret(this.SP_APP_RESOURCE_ID)).value; } } \ No newline at end of file diff --git a/src/server/server.ts b/src/server/server.ts index acf1ae585862b009ea5a5b1cc38a1714dc688054..35c067fe12567d59955c15ad8c206f0f196ec1ad 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -115,7 +115,10 @@ export class Server { // the imptoken endpoints have been marked as obsoleted and will be deprecated with the // next service upgrade (v3>v4) if (!req.headers.authorization) { - if(!((req.method === 'PUT' && req.url.endsWith('imptoken')) || req.url.endsWith('svcstatus'))) { + const imptokenCall = (req.method === 'PUT' && req.url.endsWith('imptoken')); + const statusCall = req.url.endsWith('svcstatus'); + const readinessCall = req.url.endsWith('readiness'); + if(!(imptokenCall || statusCall || readinessCall)) { Response.writeError(res, Error.make( Error.Status.UNAUTHENTICATED, 'Unauthenticated Access. Authorizations not found in the request.')); diff --git a/src/services/general/handler.ts b/src/services/general/handler.ts index daf80059ccfd39d7549fbc5e082ebb1330a1f542..b0943ddb712f8340a9b65fd2b886ef7481c87372 100644 --- a/src/services/general/handler.ts +++ b/src/services/general/handler.ts @@ -17,22 +17,38 @@ import { Request as expRequest, Response as expResponse } from 'express'; import { Error, Response } from '../../shared'; import { GeneralOP } from './optype'; +import { Config } from '../../cloud'; +import { AzureCredentials } from '../../cloud/providers/azure/credentials'; +import { AzureConfig } from '../../cloud/providers/azure'; export class GeneralHandler { // handler for the [ /svcstatus ] endpoints public static async handler(req: expRequest, res: expResponse, op: GeneralOP) { - try { - if (op === GeneralOP.Status) { Response.writeOK(res, 'service OK'); } else if (op === GeneralOP.Access) { Response.writeOK(res, { status: 'running' }); - } else { throw (Error.make(Error.Status.UNKNOWN, 'Internal Server Error')); } - + } else if (op === GeneralOP.Readiness) { + await this.handleReadinessCheck(req, res); + } else { + throw (Error.make(Error.Status.UNKNOWN, 'Internal Server Error')); + } } catch (error) { Response.writeError(res, error); } + } + private static async handleReadinessCheck(req: expRequest, res: expResponse){ + if(Config.CLOUDPROVIDER === 'azure') { + const credentials = AzureCredentials.getCredential(); + const scope = AzureConfig.SP_APP_RESOURCE_ID; + credentials.getToken(`${scope}/.default`) + .then(token => Response.writeOK(res, {ready: true})) + .catch(err => Response.writeError(res, this.serviceUnavailableError({ready: false}))) + } } + private static serviceUnavailableError(msg: any): Error { + return Error.make(Error.Status.NOT_AVAILABLE, String(msg)) + } } diff --git a/src/services/general/optype.ts b/src/services/general/optype.ts index 596519fe4a691cbf73564b95fdb88f2eb80eea9a..359cb1f5472ba912090d07d187b1c9658a073e42 100644 --- a/src/services/general/optype.ts +++ b/src/services/general/optype.ts @@ -14,4 +14,4 @@ // limitations under the License. // ============================================================================ -export enum GeneralOP { Status, Access } +export enum GeneralOP { Status, Access, Readiness } diff --git a/src/services/general/service.ts b/src/services/general/service.ts index 93644aaa7d2e307d423427252e3f494811836c54..25353210adf9df3193c100d5af24617de769784b 100644 --- a/src/services/general/service.ts +++ b/src/services/general/service.ts @@ -30,4 +30,9 @@ router.get('/access', async (req: expRequest, res: expResponse) => { await GeneralHandler.handler(req, res, GeneralOP.Access); }); +// get the service readiness status response [jwt not required by esp] +router.get('/readiness', async (req: expRequest, res: expResponse) => { + await GeneralHandler.handler(req, res, GeneralOP.Readiness); +}); + export { router as GeneralRouter }; diff --git a/src/shared/error.ts b/src/shared/error.ts index 03ced6584753cd3cef686a039c4b43462f716a77..a62b312fd1209ba7dbdd6e4b93fc5ad5d89be098 100644 --- a/src/shared/error.ts +++ b/src/shared/error.ts @@ -34,7 +34,8 @@ export class Error { PERMISSION_DENIED: 403, UNAUTHENTICATED: 401, UNKNOWN: 500, - NOT_IMPLEMENTED: 501 + NOT_IMPLEMENTED: 501, + NOT_AVAILABLE: 503 };