@@ -6,6 +6,8 @@ import * as exec from '@actions/exec';
66import * as os from 'os' ;
77
88const sendReportRetryCount : number = 1 ;
9+ const GetScanContextURL : string = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/github/v1/auth-push/GetScanContext?context=authOnly" ;
10+ const ContainerMappingURL : string = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/github/v1/container-mappings" ;
911
1012/**
1113 * Represents the tasks for container mapping that are used to fetch Docker images pushed in a job run.
@@ -85,6 +87,24 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
8587 dockerEvents : [ ] ,
8688 dockerImages : [ ]
8789 } ;
90+
91+ let bearerToken : string | void = await core . getIDToken ( )
92+ . then ( ( token ) => { return token ; } )
93+ . catch ( ( error ) => {
94+ throw new Error ( "Unable to get token: " + error ) ;
95+ } ) ;
96+
97+ if ( ! bearerToken ) {
98+ throw new Error ( "Empty OIDC token received" ) ;
99+ }
100+
101+ // Don't run the container mapping workload if this caller isn't an active customer.
102+ var callerIsOnboarded : boolean = await this . checkCallerIsCustomer ( bearerToken , sendReportRetryCount ) ;
103+ if ( ! callerIsOnboarded ) {
104+ core . info ( "Client is not onboarded to Defender for DevOps. Skipping container mapping workload." )
105+ return ;
106+ }
107+ core . info ( "Client is onboarded for container mapping." ) ;
88108
89109 // Initialize the commands
90110 let dockerVersionOutput = await exec . getExecOutput ( 'docker --version' ) ;
@@ -107,16 +127,6 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
107127
108128 core . debug ( "Finished data collection, starting API calls." ) ;
109129
110- let bearerToken : string | void = await core . getIDToken ( )
111- . then ( ( token ) => { return token ; } )
112- . catch ( ( error ) => {
113- throw new Error ( "Unable to get token: " + error ) ;
114- } ) ;
115-
116- if ( ! bearerToken ) {
117- throw new Error ( "Empty OIDC token received" ) ;
118- }
119-
120130 var reportSent : boolean = await this . sendReport ( JSON . stringify ( reportData ) , bearerToken , sendReportRetryCount ) ;
121131 if ( ! reportSent ) {
122132 throw new Error ( "Unable to send report to backend service" ) ;
@@ -148,6 +158,7 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
148158 * Sends a report to Defender for DevOps and retries on the specified count
149159 * @param data the data to send
150160 * @param retryCount the number of time to retry
161+ * @param bearerToken the GitHub-generated OIDC token
151162 * @returns a boolean Promise to indicate if the report was sent successfully or not
152163 */
153164 private async sendReport ( data : string , bearerToken : string , retryCount : number = 0 ) : Promise < boolean > {
@@ -175,7 +186,6 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
175186 private async _sendReport ( data : string , bearerToken : string ) : Promise < void > {
176187 return new Promise ( async ( resolve , reject ) => {
177188 let apiTime = new Date ( ) . getMilliseconds ( ) ;
178- let url : string = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/github/v1/container-mappings" ;
179189 let options = {
180190 method : 'POST' ,
181191 timeout : 2500 ,
@@ -185,9 +195,9 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
185195 'Content-Length' : data . length
186196 }
187197 } ;
188- core . debug ( `${ options [ 'method' ] . toUpperCase ( ) } ${ url } ` ) ;
198+ core . debug ( `${ options [ 'method' ] . toUpperCase ( ) } ${ ContainerMappingURL } ` ) ;
189199
190- const req = https . request ( url , options , ( res ) => {
200+ const req = https . request ( ContainerMappingURL , options , ( res ) => {
191201 let resData = '' ;
192202 res . on ( 'data' , ( chunk ) => {
193203 resData += chunk . toString ( ) ;
@@ -215,4 +225,69 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
215225 req . end ( ) ;
216226 } ) ;
217227 }
228+
229+ /**
230+ * Queries Defender for DevOps to determine if the caller is onboarded for container mapping.
231+ * @param retryCount the number of time to retry
232+ * @param bearerToken the GitHub-generated OIDC token
233+ * @returns a boolean Promise to indicate if the report was sent successfully or not
234+ */
235+ private async checkCallerIsCustomer ( bearerToken : string , retryCount : number = 0 ) : Promise < boolean > {
236+ return await this . _checkCallerIsCustomer ( bearerToken )
237+ . then ( async ( statusCode ) => {
238+ if ( statusCode == 200 ) { // Status 'OK' means the caller is an onboarded customer.
239+ return true ;
240+ } else if ( statusCode == 403 ) { // Status 'Forbidden' means caller is not a customer.
241+ return false ;
242+ } else {
243+ core . debug ( `Unexpected status code: ${ statusCode } ` ) ;
244+ return await this . retryCall ( bearerToken , retryCount ) ;
245+ }
246+ } )
247+ . catch ( async ( error ) => {
248+ core . info ( `Unexpected error: ${ error } .` ) ;
249+ return await this . retryCall ( bearerToken , retryCount ) ;
250+ } ) ;
251+ }
252+
253+ private async retryCall ( bearerToken : string , retryCount : number ) : Promise < boolean > {
254+ if ( retryCount == 0 ) {
255+ core . info ( `All retries failed.` ) ;
256+ return false ;
257+ } else {
258+ core . info ( `Retrying checkCallerIsCustomer.\nRetry count: ${ retryCount } ` ) ;
259+ retryCount -- ;
260+ return await this . checkCallerIsCustomer ( bearerToken , retryCount ) ;
261+ }
262+ }
263+
264+ private async _checkCallerIsCustomer ( bearerToken : string ) : Promise < number > {
265+ return new Promise ( async ( resolve , reject ) => {
266+ let options = {
267+ method : 'GET' ,
268+ timeout : 2500 ,
269+ headers : {
270+ 'Content-Type' : 'application/json' ,
271+ 'Authorization' : 'Bearer ' + bearerToken ,
272+ }
273+ } ;
274+ core . debug ( `${ options [ 'method' ] . toUpperCase ( ) } ${ GetScanContextURL } ` ) ;
275+
276+ const req = https . request ( GetScanContextURL , options , ( res ) => {
277+
278+ res . on ( 'end' , ( ) => {
279+ resolve ( res . statusCode ) ;
280+ } ) ;
281+ res . on ( 'data' , function ( d ) {
282+ } ) ;
283+ } ) ;
284+
285+ req . on ( 'error' , ( error ) => {
286+ reject ( new Error ( `Error calling url: ${ error } ` ) ) ;
287+ } ) ;
288+
289+ req . end ( ) ;
290+ } ) ;
291+ }
292+
218293}
0 commit comments