@@ -6,6 +6,8 @@ import * as exec from '@actions/exec';
6
6
import * as os from 'os' ;
7
7
8
8
const 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" ;
9
11
10
12
/**
11
13
* 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 {
85
87
dockerEvents : [ ] ,
86
88
dockerImages : [ ]
87
89
} ;
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." ) ;
88
108
89
109
// Initialize the commands
90
110
let dockerVersionOutput = await exec . getExecOutput ( 'docker --version' ) ;
@@ -107,16 +127,6 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
107
127
108
128
core . debug ( "Finished data collection, starting API calls." ) ;
109
129
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
-
120
130
var reportSent : boolean = await this . sendReport ( JSON . stringify ( reportData ) , bearerToken , sendReportRetryCount ) ;
121
131
if ( ! reportSent ) {
122
132
throw new Error ( "Unable to send report to backend service" ) ;
@@ -148,6 +158,7 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
148
158
* Sends a report to Defender for DevOps and retries on the specified count
149
159
* @param data the data to send
150
160
* @param retryCount the number of time to retry
161
+ * @param bearerToken the GitHub-generated OIDC token
151
162
* @returns a boolean Promise to indicate if the report was sent successfully or not
152
163
*/
153
164
private async sendReport ( data : string , bearerToken : string , retryCount : number = 0 ) : Promise < boolean > {
@@ -175,7 +186,6 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
175
186
private async _sendReport ( data : string , bearerToken : string ) : Promise < void > {
176
187
return new Promise ( async ( resolve , reject ) => {
177
188
let apiTime = new Date ( ) . getMilliseconds ( ) ;
178
- let url : string = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/github/v1/container-mappings" ;
179
189
let options = {
180
190
method : 'POST' ,
181
191
timeout : 2500 ,
@@ -185,9 +195,9 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
185
195
'Content-Length' : data . length
186
196
}
187
197
} ;
188
- core . debug ( `${ options [ 'method' ] . toUpperCase ( ) } ${ url } ` ) ;
198
+ core . debug ( `${ options [ 'method' ] . toUpperCase ( ) } ${ ContainerMappingURL } ` ) ;
189
199
190
- const req = https . request ( url , options , ( res ) => {
200
+ const req = https . request ( ContainerMappingURL , options , ( res ) => {
191
201
let resData = '' ;
192
202
res . on ( 'data' , ( chunk ) => {
193
203
resData += chunk . toString ( ) ;
@@ -215,4 +225,69 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
215
225
req . end ( ) ;
216
226
} ) ;
217
227
}
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
+
218
293
}
0 commit comments