8
8
"crypto/rsa"
9
9
"crypto/sha256"
10
10
"crypto/x509"
11
+ "crypto/x509/pkix"
11
12
"database/sql"
12
13
"encoding/base64"
13
14
"encoding/json"
@@ -24,6 +25,7 @@ import (
24
25
"time"
25
26
26
27
"cloud.google.com/go/compute/metadata"
28
+ "github.com/fullsailor/pkcs7"
27
29
"github.com/golang-jwt/jwt"
28
30
"github.com/google/uuid"
29
31
"github.com/moby/moby/pkg/namesgenerator"
@@ -49,9 +51,10 @@ import (
49
51
)
50
52
51
53
type Options struct {
52
- AWSInstanceIdentity awsidentity.Certificates
53
- GoogleInstanceIdentity * idtoken.Validator
54
- SSHKeygenAlgorithm gitsshkey.Algorithm
54
+ AWSCertificates awsidentity.Certificates
55
+ AzureCertificates x509.VerifyOptions
56
+ GoogleTokenValidator * idtoken.Validator
57
+ SSHKeygenAlgorithm gitsshkey.Algorithm
55
58
}
56
59
57
60
// New constructs an in-memory coderd instance and returns
@@ -60,11 +63,11 @@ func New(t *testing.T, options *Options) *codersdk.Client {
60
63
if options == nil {
61
64
options = & Options {}
62
65
}
63
- if options .GoogleInstanceIdentity == nil {
66
+ if options .GoogleTokenValidator == nil {
64
67
ctx , cancelFunc := context .WithCancel (context .Background ())
65
68
t .Cleanup (cancelFunc )
66
69
var err error
67
- options .GoogleInstanceIdentity , err = idtoken .NewValidator (ctx , option .WithoutAuthentication ())
70
+ options .GoogleTokenValidator , err = idtoken .NewValidator (ctx , option .WithoutAuthentication ())
68
71
require .NoError (t , err )
69
72
}
70
73
@@ -117,8 +120,9 @@ func New(t *testing.T, options *Options) *codersdk.Client {
117
120
Database : db ,
118
121
Pubsub : pubsub ,
119
122
120
- AWSCertificates : options .AWSInstanceIdentity ,
121
- GoogleTokenValidator : options .GoogleInstanceIdentity ,
123
+ AWSCertificates : options .AWSCertificates ,
124
+ AzureCertificates : options .AzureCertificates ,
125
+ GoogleTokenValidator : options .GoogleTokenValidator ,
122
126
SSHKeygenAlgorithm : options .SSHKeygenAlgorithm ,
123
127
TURNServer : turnServer ,
124
128
})
@@ -414,6 +418,65 @@ func NewAWSInstanceIdentity(t *testing.T, instanceID string) (awsidentity.Certif
414
418
}
415
419
}
416
420
421
+ // NewAzureInstanceIdentity returns a metadata client and ID token validator for faking
422
+ // instance authentication for Azure.
423
+ func NewAzureInstanceIdentity (t * testing.T , instanceID string ) (x509.VerifyOptions , * http.Client ) {
424
+ privateKey , err := rsa .GenerateKey (rand .Reader , 2048 )
425
+ require .NoError (t , err )
426
+
427
+ rawCertificate , err := x509 .CreateCertificate (rand .Reader , & x509.Certificate {
428
+ SerialNumber : big .NewInt (2022 ),
429
+ NotAfter : time .Now ().AddDate (1 , 0 , 0 ),
430
+ Subject : pkix.Name {
431
+ CommonName : "metadata.azure.com" ,
432
+ },
433
+ }, & x509.Certificate {}, & privateKey .PublicKey , privateKey )
434
+ require .NoError (t , err )
435
+
436
+ certificate , err := x509 .ParseCertificate (rawCertificate )
437
+ require .NoError (t , err )
438
+
439
+ signed , err := pkcs7 .NewSignedData ([]byte (`{"vmId":"` + instanceID + `"}` ))
440
+ require .NoError (t , err )
441
+ err = signed .AddSigner (certificate , privateKey , pkcs7.SignerInfoConfig {})
442
+ require .NoError (t , err )
443
+ signatureRaw , err := signed .Finish ()
444
+ require .NoError (t , err )
445
+ signature := make ([]byte , base64 .StdEncoding .EncodedLen (len (signatureRaw )))
446
+ base64 .StdEncoding .Encode (signature , signatureRaw )
447
+
448
+ payload , err := json .Marshal (codersdk.AzureInstanceIdentityToken {
449
+ Signature : string (signature ),
450
+ Encoding : "pkcs7" ,
451
+ })
452
+ require .NoError (t , err )
453
+
454
+ certPool := x509 .NewCertPool ()
455
+ certPool .AddCert (certificate )
456
+
457
+ return x509.VerifyOptions {
458
+ Intermediates : certPool ,
459
+ Roots : certPool ,
460
+ }, & http.Client {
461
+ Transport : roundTripper (func (r * http.Request ) (* http.Response , error ) {
462
+ // Only handle metadata server requests.
463
+ if r .URL .Host != "169.254.169.254" {
464
+ return http .DefaultTransport .RoundTrip (r )
465
+ }
466
+ switch r .URL .Path {
467
+ case "/metadata/attested/document" :
468
+ return & http.Response {
469
+ StatusCode : http .StatusOK ,
470
+ Body : io .NopCloser (bytes .NewReader (payload )),
471
+ Header : make (http.Header ),
472
+ }, nil
473
+ default :
474
+ panic ("unhandled route: " + r .URL .Path )
475
+ }
476
+ }),
477
+ }
478
+ }
479
+
417
480
func randomUsername () string {
418
481
return strings .ReplaceAll (namesgenerator .GetRandomName (0 ), "_" , "-" )
419
482
}
0 commit comments