Source file util.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
open! Values
open! Awso.Import

(* In sha1_insecure below, [@alert "-crypto"] suppresses the "SHA1 is broken" alert
   from Cryptokit. We know. AWS credential file caching require this for cache lookups still,
   unfortunately. *)

let sha1_insecure s =
  (let h = Cryptokit.Hash.sha1 () in
   let hh = Cryptokit.hash_string h s in
   Cryptokit.transform_string (Cryptokit.Hexa.encode ()) hh)
  [@alert "-crypto"]
;;

let format_utc (t : float) =
  let tm = Unix.gmtime t in
  sprintf
    "%04d-%02d-%02d %02d:%02d:%02dZ"
    (tm.tm_year + 1900)
    (tm.tm_mon + 1)
    tm.tm_mday
    tm.tm_hour
    tm.tm_min
    tm.tm_sec
;;

let validate_expiration expiration =
  let expiration =
    expiration
    |> Option.value_exn
         ~message:
           "Sso.get_role_credentials returned roleCredentials with empty expiration??"
    |> Int64.to_float
  in
  let now = Unix.gettimeofday () in
  if Float.( < ) expiration now
  then
    failwithf
      "Sso.get_role_credentials returned roleCredentials that were already expired! %s < \
       now (%s)"
      (format_utc expiration)
      (format_utc now)
      ()
;;

let get_cached_sso_token_file_path ~cfg =
  let fn =
    match cfg.Awso.Cfg.aws_session_token with
    | None | Some "" -> (
      match cfg.Awso.Cfg.sso_start_url with
      | None | Some "" -> None
      | Some x -> Some x)
    | Some x -> Some x
  in
  match fn with
  | None ->
    failwithf
      "No cached SSO credentials found for URL %s; run `aws sso login`"
      (cfg.Awso.Cfg.sso_start_url |> Option.value ~default:"<none>")
      ()
  | Some fn ->
    let sha1 = sha1_insecure fn in
    sprintf "%s/.aws/sso/cache/%s.json" (Stdlib.Sys.getenv "HOME") sha1
;;

let get_sso_role_request_and_cfg_exn ~cfg ~cached_sso_token_file jsonstr =
  let json = Yojson.Safe.from_string jsonstr in
  let member_string member =
    match Yojson.Safe.Util.member member json with
    | `Null -> failwithf "No '%s' found in %s" member cached_sso_token_file ()
    | `String access_token -> access_token
    | _ ->
      failwithf
        "'%s' from %s has unexpected type; wanted string"
        member
        cached_sso_token_file
        ()
  in
  let request =
    GetRoleCredentialsRequest.make
      ~accountId:
        (cfg.Awso.Cfg.sso_account_id
         |> Option.value_exn ~message:"No 'sso_account_id' set in credentials")
      ~accessToken:(member_string "accessToken")
      ~roleName:
        (cfg.Awso.Cfg.sso_role_name
         |> Option.value_exn ~message:"No 'sso_role_name' set in credentials")
      ()
  in
  let sso_region = member_string "region" |> Awso.Region.of_string in
  let cfg = { cfg with region = Some sso_region } in
  request, cfg
;;

let get_sso_role_request_and_cfg ~cfg ~cached_sso_token_file jsonstr =
  Result.try_with (fun () ->
    get_sso_role_request_and_cfg_exn ~cfg ~cached_sso_token_file jsonstr)
;;

let parse_role_credentials_response_exn = function
  | Ok ({ roleCredentials } : GetRoleCredentialsResponse.t) -> roleCredentials
  | Error (`UnauthorizedException (ue : UnauthorizedException.t)) ->
    eprintf "Unauthorized: %s\n" (Option.value ~default:"<no message given>" ue.message);
    eprintf "Maybe you need to re-run `aws sso login`?\n";
    exit 1
  | Error err ->
    failwithf
      "Sso.get_role_credentials: AWS call: %s"
      (err |> GetRoleCredentialsResponse.error_to_json |> Yojson.Safe.to_string)
      ()
;;

let update_cfg_with_role_credentials_exn ~(cfg : Awso.Cfg.t) role_credentials =
  let ({ accessKeyId; secretAccessKey; sessionToken; expiration } : RoleCredentials.t) =
    role_credentials
    |> Option.value_exn
         ~message:"Sso.get_role_credentials returned empty roleCredentials??"
  in
  let () = validate_expiration expiration in
  let not_empty s v =
    match v with
    | None | Some "" -> failwithf "Sso.get_role_credentials returned empty %s" s ()
    | Some _ -> v
  in
  { cfg with
    aws_access_key_id = not_empty "accessKeyId" accessKeyId
  ; aws_secret_access_key = not_empty "secretAccessKey" secretAccessKey
  ; aws_session_token = not_empty "sessionToken" sessionToken
  }
;;

let update_cfg_with_role_credentials ~(cfg : Awso.Cfg.t) role_credentials =
  Result.try_with (fun () -> update_cfg_with_role_credentials_exn ~cfg role_credentials)
;;