go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers-sdk/v1/upstream/sts.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package upstream 5 6 import ( 7 "context" 8 "crypto/rand" 9 "crypto/sha256" 10 "encoding/hex" 11 "net/url" 12 "time" 13 14 "go.mondoo.com/cnquery/providers/os/connection/ssh/signers" 15 "go.mondoo.com/ranger-rpc" 16 "golang.org/x/crypto/ssh" 17 ) 18 19 func ExchangeSSHKey(apiEndpoint string, identityMrn string, resourceMrn string) (*ServiceAccountCredentials, error) { 20 stsClient, err := NewSecureTokenServiceClient(apiEndpoint, ranger.DefaultHttpClient()) 21 if err != nil { 22 return nil, err 23 } 24 25 claims := &Claims{ 26 Subject: identityMrn, 27 Resource: resourceMrn, 28 Exp: time.Now().Add(5 * time.Minute).Format(time.RFC3339), 29 Iat: time.Now().Format(time.RFC3339), 30 } 31 32 // fetch all signers from ssh 33 sshSigners := signers.GetSignersFromSSHAgent() 34 35 signatures, err := signClaims(claims, sshSigners...) 36 if err != nil { 37 return nil, err 38 } 39 40 resp, err := stsClient.ExchangeSSH(context.Background(), &ExchangeSSHKeyRequest{ 41 Claims: claims, 42 Signatures: signatures, 43 }) 44 if err != nil { 45 return nil, err 46 } 47 return &ServiceAccountCredentials{ 48 Mrn: resp.Mrn, 49 ParentMrn: resp.ParentMrn, 50 PrivateKey: resp.PrivateKey, 51 Certificate: resp.Certificate, 52 ApiEndpoint: resp.ApiEndpoint, 53 }, nil 54 } 55 56 // signClaims implements claims signing with ssh.Signer 57 // 58 // To generate a new SSH key use: 59 // ssh-keygen -t ed25519 -C "your_email@example.com" 60 func signClaims(claims *Claims, signer ...ssh.Signer) ([]*SshSignature, error) { 61 data, err := HashClaimsSha256(claims) 62 if err != nil { 63 return nil, err 64 } 65 66 signatures := make([]*SshSignature, 0, len(signer)) 67 for i := range signer { 68 sig := signer[i] 69 70 // sign content 71 sshSign, err := sig.Sign(rand.Reader, data) 72 if err != nil { 73 return nil, err 74 } 75 76 signatures = append(signatures, &SshSignature{ 77 Alg: "x5t#S256", 78 Kid: ssh.FingerprintSHA256(sig.PublicKey()), 79 Sig: hex.EncodeToString(ssh.Marshal(sshSign)), 80 }) 81 } 82 return signatures, nil 83 } 84 85 // sha256hash returns a hash of the claims data 86 func sha256hash(data []byte) []byte { 87 hash := sha256.New() 88 hash.Write(data) 89 return hash.Sum(nil) 90 } 91 92 // builds a canonical string from the claims to ensure that the hash is always the same and keys cannot be swapped 93 func buildCanonicalString(claims *Claims) string { 94 params := url.Values{} 95 params.Add("subject", claims.Subject) 96 params.Add("resource", claims.Resource) 97 params.Add("exp", claims.Exp) 98 params.Add("iat", claims.Iat) 99 return params.Encode() + "\n" 100 } 101 102 // HashClaims returns a hash of the claims data 103 func HashClaimsSha256(claims *Claims) ([]byte, error) { 104 strToHash := buildCanonicalString(claims) 105 return []byte(hex.EncodeToString(sha256hash([]byte(strToHash)))), nil 106 }