1 /*===========================================================================
2 *
3 * PUBLIC DOMAIN NOTICE
4 * National Center for Biotechnology Information
5 *
6 * This software/database is a "United States Government Work" under the
7 * terms of the United States Copyright Act. It was written as part of
8 * the author's official duties as a United States Government employee and
9 * thus cannot be copyrighted. This software/database is freely available
10 * to the public for use. The National Library of Medicine and the U.S.
11 * Government have not placed any restriction on its use or reproduction.
12 *
13 * Although all reasonable efforts have been taken to ensure the accuracy
14 * and reliability of the software and data, the NLM and the U.S.
15 * Government do not and cannot warrant the performance or results that
16 * may be obtained by using this software or data. The NLM and the U.S.
17 * Government disclaim all warranties, express or implied, including
18 * warranties of performance, merchantability or fitness for any particular
19 * purpose.
20 *
21 * Please cite the author in any work or product based on this material.
22 *
23 * ===========================================================================
24 *
25 */
26
27 struct AWS;
28 #define CLOUD_IMPL struct AWS
29
30 #include <cloud/extern.h>
31 #include <cloud/impl.h>
32
33 #include <cloud/aws.h>
34
35 #include <klib/debug.h> /* DBGMSG */
36 #include <klib/rc.h>
37 #include <klib/status.h>
38 #include <klib/strings.h> /* ENV_MAGIC_CE_TOKEN */
39 #include <klib/text.h>
40 #include <klib/printf.h>
41
42 #include <kfg/config.h>
43 #include <kfg/kfg-priv.h>
44 #include <kfg/properties.h>
45 #include <kns/endpoint.h>
46 #include <kns/socket.h>
47 #include <kns/http.h>
48 #include <kfs/directory.h>
49 #include <kfs/file.h>
50
51 #include <assert.h>
52
53 #include "aws-priv.h" /* AWSDoAuthentication */
54 #include "cloud-cmn.h" /* KNSManager_Read */
55 #include "cloud-priv.h"
56
57 static rc_t PopulateCredentials ( AWS * self );
58
59 /* Destroy
60 */
61 static
AWSDestroy(AWS * self)62 rc_t CC AWSDestroy ( AWS * self )
63 {
64 free ( self -> region );
65 free ( self -> output );
66 free ( self -> access_key_id );
67 free ( self -> secret_access_key );
68 free ( self -> profile );
69 return CloudWhack ( & self -> dad );
70 }
71
KNSManager_GetAWSLocation(const KNSManager * self,char * buffer,size_t bsize)72 static rc_t KNSManager_GetAWSLocation(
73 const KNSManager * self, char *buffer, size_t bsize)
74 {
75 return KNSManager_Read(self, buffer, bsize,
76 "http://169.254.169.254/latest/meta-data/placement/availability-zone",
77 NULL, NULL);
78 }
79
80 /* envCE
81 * Get Compute Environment Token from environment variable
82 *
83 * NB. this is a one-shot function, but atomic is not important
84 */
envCE()85 static char const *envCE()
86 {
87 static bool firstTime = true;
88 char const *const env = firstTime ? getenv(ENV_MAGIC_CE_TOKEN) : NULL;
89 firstTime = false;
90 if ( env != NULL && *env != 0 )
91 DBGMSG(DBG_VFS, DBG_FLAG(DBG_VFS_PATH),
92 ("Got location from environment\n"));
93 return env;
94 }
95
96 /* readCE
97 * Get Compute Environment Token by reading from provider network
98 */
readCE(AWS const * const self,size_t size,char location[])99 static rc_t readCE(AWS const *const self, size_t size, char location[])
100 {
101 char document[4096] = "";
102 char pkcs7[4096] = "";
103 rc_t rc;
104
105 DBGMSG(DBG_VFS, DBG_FLAG(DBG_VFS_PATH),
106 ("Reading location from provider\n"));
107 rc = KNSManager_Read(self->dad.kns, document, sizeof document,
108 "http://169.254.169.254/latest/dynamic/instance-identity/document",
109 NULL, NULL);
110 if (rc) return rc;
111
112 rc = KNSManager_Read(self->dad.kns, pkcs7, sizeof pkcs7,
113 "http://169.254.169.254/latest/dynamic/instance-identity/pkcs7",
114 NULL, NULL);
115 if (rc) return rc;
116
117 return MakeLocation(pkcs7, document, location, size);
118 }
119
120 /* MakeComputeEnvironmentToken
121 * contact cloud provider to get proof of execution environment in form of a token
122 */
123 static
AWSMakeComputeEnvironmentToken(const AWS * self,const String ** ce_token)124 rc_t CC AWSMakeComputeEnvironmentToken ( const AWS * self, const String ** ce_token )
125 {
126 assert(self);
127
128 if (!self->dad.user_agrees_to_reveal_instance_identity)
129 return RC(rcCloud, rcProvider, rcIdentifying,
130 rcCondition, rcUnauthorized);
131 else {
132 char const *const env = envCE();
133 char location[4096] = "";
134 rc_t const rc = env == NULL ? readCE(self, sizeof(location), location) : 0;
135 if (rc == 0) {
136 String s;
137 StringInitCString(&s, env != NULL ? env : location);
138 return StringCopy(ce_token, &s);
139 }
140 return rc;
141 }
142 }
143
144 /* AwsGetLocation
145 */
AwsGetLocation(const AWS * self,const String ** location)146 static rc_t AwsGetLocation(const AWS * self, const String ** location)
147 {
148 rc_t rc = 0;
149
150 char zone[64] = "";
151 char buffer[64] = "";
152
153 assert(self);
154
155 rc = KNSManager_GetAWSLocation(self->dad.kns, zone, sizeof zone);
156
157 if (rc == 0)
158 rc = string_printf(buffer, sizeof buffer, NULL, "s3.%s", zone);
159
160 if (rc == 0) {
161 String s;
162 StringInitCString(&s, buffer);
163 rc = StringCopy(location, &s);
164 }
165
166 return rc;
167 }
168
169
170 /* AddComputeEnvironmentTokenForSigner
171 * prepare a request object with a compute environment token
172 * for use by an SDL-associated "signer" service
173 */
174 static
AWSAddComputeEnvironmentTokenForSigner(const AWS * self,KClientHttpRequest * req)175 rc_t CC AWSAddComputeEnvironmentTokenForSigner ( const AWS * self, KClientHttpRequest * req )
176 {
177 const String * ce_token = NULL;
178 rc_t rc = AWSMakeComputeEnvironmentToken(self, &ce_token);
179
180 if (rc == 0) {
181 rc = KHttpRequestAddPostParam(req, "ident=%S", ce_token);
182 StringWhack(ce_token);
183 }
184
185 return rc;
186 }
187
188 /* AddAuthentication
189 * prepare a request object with credentials for authentication
190 */
AWSAddAuthentication(const AWS * self,KClientHttpRequest * req,const char * http_method)191 static rc_t CC AWSAddAuthentication ( const AWS * self,
192 KClientHttpRequest * req, const char * http_method )
193 {
194 return AWSDoAuthentication(self, req, http_method, false);
195 }
196
197 /* AddUserPaysCredentials
198 * prepare a request object with credentials for user-pays
199 */
200 static
AWSAddUserPaysCredentials(const AWS * self,KClientHttpRequest * req,const char * http_method)201 rc_t CC AWSAddUserPaysCredentials ( const AWS * self, KClientHttpRequest * req, const char * http_method )
202 {
203 assert(self);
204
205 if (self->dad.user_agrees_to_pay)
206 return AWSDoAuthentication(self, req, http_method, true);
207 else
208 return 0;
209 }
210
211 static Cloud_vt_v1 AWS_vt_v1 =
212 {
213 1, 0,
214
215 AWSDestroy,
216 AWSMakeComputeEnvironmentToken,
217 AwsGetLocation,
218 AWSAddComputeEnvironmentTokenForSigner,
219 AWSAddAuthentication,
220 AWSAddUserPaysCredentials
221 };
222
223
224 /* MakeAWS
225 * make an instance of an AWS cloud interface
226 */
CloudMgrMakeAWS(const CloudMgr * self,AWS ** p_aws)227 LIB_EXPORT rc_t CC CloudMgrMakeAWS ( const CloudMgr * self, AWS ** p_aws )
228 {
229 rc_t rc;
230 //TODO: check self, aws
231 AWS * aws = calloc ( 1, sizeof * aws );
232 if ( aws == NULL )
233 {
234 rc = RC ( rcCloud, rcMgr, rcAllocating, rcMemory, rcExhausted );
235 }
236 else
237 {
238 /* capture from self->kfg */
239 bool user_agrees_to_pay = false;
240 bool user_agrees_to_reveal_instance_identity = false;
241 if (self != NULL) {
242 KConfig_Get_User_Accept_Aws_Charges(self->kfg,
243 &user_agrees_to_pay);
244 KConfig_Get_Report_Cloud_Instance_Identity(self->kfg,
245 &user_agrees_to_reveal_instance_identity);
246 }
247
248 rc = CloudInit ( & aws -> dad, ( const Cloud_vt * ) & AWS_vt_v1, "AWS", self -> kns, user_agrees_to_pay,
249 user_agrees_to_reveal_instance_identity );
250 if ( rc == 0 )
251 {
252 rc = PopulateCredentials( aws );
253 if ( rc == 0 )
254 {
255 * p_aws = aws;
256 }
257 else
258 {
259 CloudRelease( & aws -> dad );
260 }
261 }
262 else
263 {
264 free ( aws );
265 }
266
267 }
268
269 return rc;
270 }
271
272 /* AddRef
273 * Release
274 */
AWSAddRef(const AWS * self)275 LIB_EXPORT rc_t CC AWSAddRef ( const AWS * self )
276 {
277 return CloudAddRef ( & self -> dad );
278 }
279
AWSRelease(const AWS * self)280 LIB_EXPORT rc_t CC AWSRelease ( const AWS * self )
281 {
282 return CloudRelease ( & self -> dad );
283 }
284
285 /* Cast
286 * cast from a Cloud to an AWS type or vice versa
287 * allows us to apply cloud-specific interface to cloud object
288 *
289 * returns a new reference, meaning the "self" must still be released
290 */
AWSToCloud(const AWS * cself,Cloud ** cloud)291 LIB_EXPORT rc_t CC AWSToCloud ( const AWS * cself, Cloud ** cloud )
292 {
293 rc_t rc;
294
295 if ( cloud == NULL )
296 rc = RC ( rcCloud, rcProvider, rcCasting, rcParam, rcNull );
297 else
298 {
299 if ( cself == NULL )
300 rc = 0;
301 else
302 {
303 AWS * self = ( AWS * ) cself;
304
305 rc = CloudAddRef ( & self -> dad );
306 if ( rc == 0 )
307 {
308 * cloud = & self -> dad;
309 return 0;
310 }
311 }
312
313 * cloud = NULL;
314 }
315
316 return rc;
317 }
318
319 /* WithinAWS
320 * answers true if within AWS
321 */
CloudMgrWithinAWS(const CloudMgr * self)322 bool CloudMgrWithinAWS ( const CloudMgr * self )
323 {
324 #if 0
325 KEndPoint ep;
326 /* describe address 169.254.169.254 on port 80 */
327 KNSManagerInitIPv4Endpoint ( self -> kns, & ep,
328 ( ( 169 << 24 ) |
329 ( 254 << 16 ) |
330 ( 169 << 8 ) |
331 ( 254 << 0 ) ), 80 );
332
333 #endif
334
335 char buffer[999] = "";
336
337 assert(self);
338
339 return KNSManager_GetAWSLocation(self->kns, buffer, sizeof buffer) == 0;
340 }
341
342 /*** Finding/loading credentials */
343
aws_extract_key_value_pair(const String * source,String * key,String * val)344 static rc_t aws_extract_key_value_pair (
345 const String *source, String *key, String *val )
346 {
347 String k, v;
348 const char *start = source->addr;
349 const char *end = start + source->size;
350
351 char *eql = string_chr ( start, source->size, '=' );
352 if ( eql == NULL )
353 return RC (rcCloud, rcChar, rcSearching, rcFormat, rcInvalid );
354
355 /* key */
356 StringInit ( &k, start, eql - start, string_len ( start, eql - start ) );
357 StringTrim ( &k, key );
358
359 start = eql + 1;
360
361 /* value */
362 StringInit ( &v, start, end - start, string_len ( start, end - start ) );
363 StringTrim ( &v, val );
364
365 return 0;
366 }
367
368 /*TODO: improve error handling (at least report) */
aws_parse_file(AWS * self,const KFile * cred_file)369 static void aws_parse_file ( AWS * self, const KFile *cred_file )
370 {
371 rc_t rc;
372 size_t buf_size;
373 size_t num_read;
374
375 char * buffer;
376 uint64_t file_size;
377
378 assert ( self != NULL );
379 assert ( self -> profile != NULL );
380
381 rc = KFileSize ( cred_file, &file_size );
382 if ( rc != 0 )
383 return;
384
385 buf_size = ( size_t ) file_size;
386 if ( sizeof buf_size < sizeof file_size && ( uint64_t ) buf_size != file_size )
387 return;
388
389 buffer = malloc ( buf_size );
390 rc = KFileReadAll ( cred_file, 0, buffer, buf_size, &num_read );
391
392 if ( rc != 0 )
393 {
394 free ( buffer );
395 }
396 else
397 {
398 String bracket;
399 String profile;
400 const String *temp1;
401 const String *brack_profile;
402
403 const char *start = buffer;
404 const char *end = start + buf_size;
405 const char *sep = start;
406 bool in_profile = false;
407
408 CONST_STRING ( &bracket, "[" );
409
410 StringInitCString( & profile, self -> profile );
411
412 StringConcat ( &temp1, &bracket, &profile );
413 CONST_STRING ( &bracket, "]" );
414 StringConcat ( &brack_profile, temp1, &bracket );
415
416 --sep;
417
418
419 for ( ; start < end; start = sep + 1 ) {
420 rc_t rc;
421 String string, trim;
422 String key, value;
423 String access_key_id, secret_access_key;
424 String region, output;
425
426 sep = string_chr ( start, end - start, '\n' );
427 if ( sep == NULL ) sep = (char *)end;
428
429 StringInit (
430 &string, start, sep - start, string_len ( start, sep - start ) );
431
432 StringTrim ( &string, &trim );
433 /* check for empty line and skip */
434 if ( StringLength ( &trim ) == 0 ) continue;
435 /*
436 {
437 char *p = string_dup ( trim.addr, StringLength ( &trim ) );
438 fprintf ( stderr, "line: %s\n", p );
439 free ( p );
440 }
441 */
442 /* check for comment line and skip */
443 if ( trim.addr[0] == '#' ) continue;
444
445 /* check for [profile] line */
446 if ( trim.addr[0] == '[' ) {
447 in_profile = StringEqual ( &trim, brack_profile );
448 continue;
449 }
450
451 if ( !in_profile ) continue;
452
453 /* check for key/value pairs and skip if none found */
454 rc = aws_extract_key_value_pair ( &trim, &key, &value );
455 if ( rc != 0 ) continue;
456
457 /* now check keys we are looking for and populate the node*/
458
459 CONST_STRING ( &access_key_id, "aws_access_key_id" );
460 CONST_STRING ( &secret_access_key, "aws_secret_access_key" );
461
462 if ( StringCaseEqual ( &key, &access_key_id ) ) {
463 free ( self -> access_key_id );
464 self -> access_key_id = string_dup ( value . addr, value . size );
465 }
466
467 if ( StringCaseEqual ( &key, &secret_access_key ) ) {
468 free ( self -> secret_access_key );
469 self -> secret_access_key = string_dup ( value . addr, value . size );
470 }
471
472 CONST_STRING ( ®ion, "region" );
473 CONST_STRING ( &output, "output" );
474
475 if ( StringCaseEqual ( &key, ®ion ) ) {
476 free ( self -> region );
477 self -> region = string_dup ( value . addr, value . size );
478 }
479 if ( StringCaseEqual ( &key, &output ) ) {
480 free ( self -> output );
481 self -> output = string_dup ( value . addr, value . size );
482 }
483
484 }
485
486 StringWhack ( temp1 );
487 StringWhack ( brack_profile );
488 free ( buffer );
489 }
490 }
491
make_home_node(char * path,size_t path_size)492 static void make_home_node ( char *path, size_t path_size )
493 {
494 size_t num_read;
495 const char *home;
496
497 KConfig * kfg;
498 rc_t rc = KConfigMakeLocal( & kfg, NULL );
499 if ( rc == 0 )
500 {
501 const KConfigNode *home_node;
502
503 /* Check to see if home node exists */
504 rc = KConfigOpenNodeRead ( kfg, &home_node, "HOME" );
505 if ( home_node == NULL ) {
506 /* just grab the HOME env variable */
507 home = getenv ( "HOME" );
508 if ( home != NULL && *home != 0 ) {
509 num_read = string_copy_measure ( path, path_size, home );
510 if ( num_read >= path_size ) path[0] = 0;
511 }
512 } else {
513 /* if it exists check for a path */
514 rc = KConfigNodeRead ( home_node, 0, path, path_size, &num_read, NULL );
515 if ( rc != 0 ) {
516 home = getenv ( "HOME" );
517 if ( home != NULL && *home != 0 ) {
518 num_read = string_copy_measure ( path, path_size, home );
519 if ( num_read >= path_size ) path[0] = 0;
520 }
521 }
522
523 KConfigNodeRelease ( home_node );
524 }
525
526 KConfigRelease ( kfg );
527 }
528 }
529
LoadCredentials(AWS * self)530 static rc_t LoadCredentials ( AWS * self )
531 {
532 const char *conf_env = getenv ( "AWS_CONFIG_FILE" );
533 const char *cred_env = getenv ( "AWS_SHARED_CREDENTIAL_FILE" );
534
535 KDirectory *wd = NULL;
536 rc_t rc = KDirectoryNativeDir ( &wd );
537 if ( rc ) return rc;
538
539 if ( conf_env && *conf_env != 0 )
540 {
541 const KFile *cred_file = NULL;
542 rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s", conf_env );
543 if ( rc == 0 )
544 {
545 aws_parse_file ( self, cred_file );
546 KFileRelease ( cred_file );
547 }
548 KDirectoryRelease ( wd );
549 return rc;
550 }
551
552 if ( cred_env && *cred_env != 0 )
553 {
554 const KFile *cred_file = NULL;
555 rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s", cred_env );
556 if ( rc == 0 )
557 {
558 aws_parse_file ( self, cred_file );
559 KFileRelease ( cred_file );
560 }
561 KDirectoryRelease ( wd );
562 return rc;
563 }
564
565 {
566 char home[4096] = "";
567 make_home_node ( home, sizeof home );
568
569 if ( home[0] != 0 )
570 {
571 char aws_path[4096] = "";
572 size_t num_writ = 0;
573 rc = string_printf ( aws_path, sizeof aws_path, &num_writ, "%s/.aws", home );
574 if ( rc == 0 && num_writ != 0 )
575 {
576 const KFile *cred_file = NULL;
577 rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s%s", aws_path, "/credentials" );
578 if ( rc == 0 )
579 {
580 aws_parse_file ( self, cred_file );
581 KFileRelease ( cred_file );
582
583 rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s%s", aws_path, "/config" );
584 if ( rc == 0 )
585 {
586 aws_parse_file ( self, cred_file );
587 KFileRelease ( cred_file );
588 }
589 }
590 }
591 }
592 }
593
594 KDirectoryRelease ( wd );
595 return rc;
596 }
597
598 //TODO: check results of strdups and string_dups
599 static
PopulateCredentials(AWS * self)600 rc_t PopulateCredentials ( AWS * self )
601 {
602 /* Check Environment first */
603 const char * aws_access_key_id = getenv ( "AWS_ACCESS_KEY_ID" );
604 const char * aws_secret_access_key = getenv ( "AWS_SECRET_ACCESS_KEY" );
605 const char * profile = getenv ( "AWS_PROFILE" );
606
607 if ( aws_access_key_id != NULL && aws_secret_access_key != NULL
608 && strlen ( aws_access_key_id ) > 0
609 && strlen ( aws_secret_access_key ) > 0 )
610 { /* Use environment variables */
611 self -> access_key_id = string_dup( aws_access_key_id, string_size( aws_access_key_id ) );
612 self -> secret_access_key = string_dup( aws_secret_access_key, string_size( aws_secret_access_key ) );
613 return 0;
614 }
615
616 /* Get Profile */
617 if ( profile != NULL && *profile != 0 )
618 {
619 self -> profile = string_dup ( profile, string_size ( profile ) );
620 }
621 else
622 {
623 KConfig * kfg;
624 rc_t rc = KConfigMakeLocal( & kfg, NULL );
625 /*KConfigPrint(kfg,0);*/
626 if ( rc == 0 )
627 {
628 char buffer[4096];
629 size_t num_writ = 0;
630 rc = KConfig_Get_Aws_Profile ( kfg, buffer, sizeof ( buffer ), &num_writ );
631 if ( rc == 0 && num_writ > 0 )
632 {
633 self -> profile = string_dup ( buffer, string_size ( buffer ) );
634 }
635 KConfigRelease( kfg );
636 }
637 }
638
639 if ( self -> profile == NULL )
640 {
641 self -> profile = strdup ( "default" );
642 }
643
644 /* OK if no credentials are found */
645 LoadCredentials ( self );
646 return 0;
647 }
648
649 #if 0
650
651 /* Check AWS_CONFIG_FILE and AWS_SHARED_CREDENTIAL_FILE, if specified check for credentials and/or profile name */
652 const char *conf_env = getenv ( "AWS_CONFIG_FILE" );
653 if ( conf_env == NULL || *conf_env == 0 )
654 {
655 conf_env = getenv ( "AWS_SHARED_CREDENTIAL_FILE" );
656 }
657 if ( conf_env && *conf_env != 0 )
658 {
659 KDirectory *wd = NULL;
660 const KFile *cred_file = NULL;
661
662 rc = KDirectoryNativeDir ( &wd );
663 if ( rc != 0 )
664 {
665 return rc;
666 }
667
668 rc = KDirectoryOpenFileRead ( wd, &cred_file, "%s", conf_env );
669 if ( rc == 0 )
670 {
671 aws_parse_file ( self, cred_file );
672 KFileRelease ( cred_file );
673 }
674 KDirectoryRelease ( wd );
675 if ( rc != 0 )
676 {
677 return rc;
678 }
679 if ( self -> access_key_id != NULL && self -> secret_access_key != NULL )
680 {
681 return 0;
682 }
683
684 /* proceed to other sources */
685 }
686
687 /* Check paths and parse */
688 char home[4096] = "";
689 make_home_node ( self, home, sizeof home );
690
691 if ( home[0] != 0 ) {
692 char path[4096] = "";
693 size_t num_writ = 0;
694 rc = string_printf ( path, sizeof path, &num_writ, "%s/.aws", home );
695 if ( rc == 0 && num_writ != 0 ) {
696 /* Use config files */
697 if ( rc == 0 ) {
698 rc = aws_find_nodes ( aws_node, path, &sprofile );
699 if ( rc ) {
700 /* OK if no .aws available */
701 rc = 0;
702 }
703 }
704 }
705 }
706
707 KConfigNodeRelease ( aws_node );
708 return rc;
709 }
710 #endif
711
CloudToAWS(const Cloud * self,AWS ** aws)712 LIB_EXPORT rc_t CC CloudToAWS ( const Cloud * self, AWS ** aws )
713 {
714 rc_t rc;
715
716 if ( aws == NULL )
717 rc = RC ( rcCloud, rcProvider, rcCasting, rcParam, rcNull );
718 else
719 {
720 if ( self == NULL )
721 rc = 0;
722 else if ( self -> vt != ( const Cloud_vt * ) & AWS_vt_v1 )
723 rc = RC ( rcCloud, rcProvider, rcCasting, rcType, rcIncorrect );
724 else
725 {
726 rc = CloudAddRef ( self );
727 if ( rc == 0 )
728 {
729 * aws = ( AWS * ) self;
730 return 0;
731 }
732 }
733
734 * aws = NULL;
735 }
736
737 return rc;
738 }
739