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  }