Skip to content

Commit 31832b8

Browse files
committed
TMP
Signed-off-by: Ciprian Hacman <ciprian@hakman.dev>
1 parent 7ad7d32 commit 31832b8

File tree

5 files changed

+71
-33
lines changed

5 files changed

+71
-33
lines changed

upup/pkg/fi/cloudup/azure/attest.go

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434

3535
"github.com/smallstep/pkcs7"
3636
expirationcache "k8s.io/client-go/tools/cache"
37+
"k8s.io/klog/v2"
3738
)
3839

3940
const (
@@ -144,9 +145,7 @@ func verifyAttestedDocument(signature string, body []byte) (*attestedData, error
144145
}
145146

146147
// verifyAttestedDocumentWithRootAndFetcher verifies a PKCS7 attested document
147-
// using the supplied root pool and intermediate fetcher. It prefers the
148-
// embedded PKCS7 certificates and only consults the fetcher when the embedded
149-
// chain does not already verify.
148+
// using the supplied root pool and intermediate fetcher.
150149
func verifyAttestedDocumentWithRootAndFetcher(signature string, body []byte, rootCertPool *x509.CertPool, fetchIntermediates func(*x509.Certificate) (*x509.CertPool, error)) (*attestedData, error) {
151150
if rootCertPool == nil {
152151
return nil, fmt.Errorf("root certificate pool is required")
@@ -160,17 +159,16 @@ func verifyAttestedDocumentWithRootAndFetcher(signature string, body []byte, roo
160159
return nil, err
161160
}
162161

163-
embeddedOnlyIntermediates := x509.NewCertPool()
164-
if err := verifySignerCertChain(signer, p7.Certificates, rootCertPool, embeddedOnlyIntermediates); err != nil {
165-
// Signer SAN was validated above; only now is it safe to hit the network.
166-
intermediateCerts, fetchErr := fetchIntermediates(signer)
167-
if fetchErr != nil {
168-
return nil, fmt.Errorf("verifying PKCS7 certificate chain: embedded=%w; fetch=%w", err, fetchErr)
169-
}
170-
if err := verifySignerCertChain(signer, p7.Certificates, rootCertPool, intermediateCerts); err != nil {
171-
return nil, fmt.Errorf("verifying PKCS7 certificate chain: %w", err)
172-
}
162+
// Signer SAN was validated above; now it is safe to hit the network.
163+
klog.V(2).Infof("Fetching intermediate certificates for signer issuer %q", signer.Issuer)
164+
intermediateCerts, err := fetchIntermediates(signer)
165+
if err != nil {
166+
return nil, fmt.Errorf("fetching intermediate certificates: %w", err)
167+
}
168+
if err := verifySignerCertChain(signer, p7.Certificates, rootCertPool, intermediateCerts); err != nil {
169+
return nil, fmt.Errorf("verifying PKCS7 certificate chain: %w", err)
173170
}
171+
klog.V(2).Infof("PKCS7 certificate chain verified for signer issuer %q", signer.Issuer)
174172

175173
return parseAndValidateAttestedDocumentContent(p7.Content, body)
176174
}
@@ -211,24 +209,29 @@ func parseAndValidatePKCS7Signer(signature string) (*pkcs7.PKCS7, *x509.Certific
211209
if err != nil {
212210
return nil, nil, fmt.Errorf("decoding PKCS7 signature: %w", err)
213211
}
212+
klog.V(4).Infof("Decoded PKCS7 signature (%d bytes)", len(sigBytes))
214213

215214
p7, err := pkcs7.Parse(sigBytes)
216215
if err != nil {
217216
return nil, nil, fmt.Errorf("parsing PKCS7 signature: %w", err)
218217
}
218+
klog.V(8).Infof("Parsed PKCS7 structure with %d embedded certificate(s)", len(p7.Certificates))
219219

220220
// Verify the PKCS7 signature against the embedded leaf certificate.
221221
if err := p7.Verify(); err != nil {
222222
return nil, nil, fmt.Errorf("verifying PKCS7 signature: %w", err)
223223
}
224+
klog.V(4).Infof("PKCS7 self-signature verified")
224225

225226
signer := p7.GetOnlySigner()
226227
if signer == nil {
227228
return nil, nil, fmt.Errorf("getting PKCS7 signer certificate")
228229
}
230+
klog.V(8).Infof("PKCS7 signer certificate: subject=%q issuer=%q SANs=%v", signer.Subject, signer.Issuer, signer.DNSNames)
229231
if err := validateAzureMetadataSignerSAN(signer); err != nil {
230232
return nil, nil, fmt.Errorf("validating PKCS7 signer SAN: %w", err)
231233
}
234+
klog.V(4).Infof("PKCS7 signer SAN validated as Azure metadata endpoint")
232235

233236
return p7, signer, nil
234237
}
@@ -248,11 +251,14 @@ func parseAndValidateAttestedDocumentContent(content []byte, body []byte) (*atte
248251
if err := json.Unmarshal(content, &data); err != nil {
249252
return nil, fmt.Errorf("unmarshalling attested data: %w", err)
250253
}
254+
klog.V(4).Infof("Attested document content: vmId=%q subscriptionId=%q nonce=%q createdOn=%q expiresOn=%q", data.VMId, data.SubscriptionId, data.Nonce, data.TimeStamp.CreatedOn, data.TimeStamp.ExpiresOn)
251255

252256
// Verify the nonce matches the request body hash (replay protection).
253-
if data.Nonce != nonceForBody(body) {
254-
return nil, fmt.Errorf("attested document nonce mismatch")
257+
expectedNonce := nonceForBody(body)
258+
if data.Nonce != expectedNonce {
259+
return nil, fmt.Errorf("attested document nonce mismatch: got=%q expected=%q", data.Nonce, expectedNonce)
255260
}
261+
klog.V(4).Infof("Attested document nonce verified")
256262

257263
// Verify the attested document has not expired.
258264
if data.TimeStamp.ExpiresOn != "" {
@@ -263,6 +269,7 @@ func parseAndValidateAttestedDocumentContent(content []byte, body []byte) (*atte
263269
if time.Now().After(expiresOn) {
264270
return nil, fmt.Errorf("attested document expired at %s", data.TimeStamp.ExpiresOn)
265271
}
272+
klog.V(4).Infof("Attested document not expired (expiresOn=%s)", expiresOn.Format(time.RFC3339))
266273
}
267274

268275
return &data, nil
@@ -319,12 +326,14 @@ func intermediateCertPoolWithCaches(signer *x509.Certificate, fetch func(*x509.C
319326
// Positive cache wins over negative: a successful later fetch overwrites
320327
// any stale negative entry, which expires on its own shorter TTL.
321328
if obj, ok, _ := positive.GetByKey(keyStr); ok {
329+
klog.V(4).Infof("Intermediate certificate cache hit (positive) for signer issuer %q", signer.Issuer)
322330
return obj.(*intermediateCertCacheEntry).pool, nil
323331
}
324332
if _, ok, _ := negative.GetByKey(keyStr); ok {
325-
return nil, fmt.Errorf("intermediate certificate fetch recently failed for signer issuer (cached)")
333+
return nil, fmt.Errorf("intermediate certificate fetch recently failed for signer issuer %q (cached)", signer.Issuer)
326334
}
327335

336+
klog.V(2).Infof("Intermediate certificate cache miss for signer issuer %q; fetching from Microsoft PKI", signer.Issuer)
328337
pool, fetchErr := fetch(signer)
329338
entry.pool = pool
330339
if fetchErr != nil {
@@ -373,19 +382,23 @@ func fetchIntermediateCertsFromBaseURL(client *http.Client, baseURL string, sign
373382
pool := x509.NewCertPool()
374383
matched := 0
375384
for _, url := range urls {
385+
klog.V(4).Infof("Fetching intermediate certificate from %s", url)
376386
cert, err := fetchCertificate(client, url)
377387
if err != nil {
378388
return nil, err
379389
}
380390
if err := validateFetchedIntermediateForSigner(signer, cert); err != nil {
391+
klog.V(4).Infof("Fetched certificate from %s did not match signer issuer: %v", url, err)
381392
continue
382393
}
394+
klog.V(4).Infof("Fetched intermediate certificate from %s matched signer issuer (subject=%q)", url, cert.Subject)
383395
pool.AddCert(cert)
384396
matched++
385397
}
386398
if matched == 0 {
387-
return nil, fmt.Errorf("no fetched intermediate certificates matched signer issuer")
399+
return nil, fmt.Errorf("no fetched intermediate certificates matched signer issuer %q", signer.Issuer)
388400
}
401+
klog.V(2).Infof("Fetched %d intermediate certificate(s) matching signer issuer %q from %d candidate URL(s)", matched, signer.Issuer, len(urls))
389402

390403
return pool, nil
391404
}

upup/pkg/fi/cloudup/azure/attest_test.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
"math/big"
3131
"net/http"
3232
"slices"
33-
"strings"
3433
"testing"
3534
"time"
3635

@@ -777,7 +776,7 @@ func TestVerifyAttestedDocumentWithCertPools(t *testing.T) {
777776
// TestVerifyAttestedDocumentWithRootAndFetcher_SkipsFetchWhenEmbeddedChainSuffices
778777
// verifies that the verifier accepts an attestation using only embedded PKCS7
779778
// certificates and does not consult Microsoft PKI in that case.
780-
func TestVerifyAttestedDocumentWithRootAndFetcher_SkipsFetchWhenEmbeddedChainSuffices(t *testing.T) {
779+
func TestVerifyAttestedDocumentWithRootAndFetcher_AlwaysFetchesIntermediates(t *testing.T) {
781780
rootCert, intermediateCert, leafCert, leafKey := testPKIChain(t)
782781

783782
body := []byte("test-body")
@@ -793,6 +792,7 @@ func TestVerifyAttestedDocumentWithRootAndFetcher_SkipsFetchWhenEmbeddedChainSuf
793792
if err != nil {
794793
t.Fatalf("marshalling attested data: %v", err)
795794
}
795+
// Include the intermediate in the PKCS7 to confirm the fetcher is still called.
796796
sig := base64.StdEncoding.EncodeToString(createTestPKCS7(t, content, leafCert, leafKey, intermediateCert))
797797

798798
rootPool := x509.NewCertPool()
@@ -801,16 +801,18 @@ func TestVerifyAttestedDocumentWithRootAndFetcher_SkipsFetchWhenEmbeddedChainSuf
801801
fetchCalls := 0
802802
result, err := verifyAttestedDocumentWithRootAndFetcher(sig, body, rootPool, func(*x509.Certificate) (*x509.CertPool, error) {
803803
fetchCalls++
804-
return nil, fmt.Errorf("unexpected fetch")
804+
pool := x509.NewCertPool()
805+
pool.AddCert(intermediateCert)
806+
return pool, nil
805807
})
806808
if err != nil {
807809
t.Fatalf("unexpected verification error: %v", err)
808810
}
809811
if result.VMId != "test-vm-id" {
810812
t.Fatalf("expected vmId %q, got %q", "test-vm-id", result.VMId)
811813
}
812-
if fetchCalls != 0 {
813-
t.Fatalf("fetch call count mismatch: got %d, want 0", fetchCalls)
814+
if fetchCalls != 1 {
815+
t.Fatalf("fetch call count mismatch: got %d, want 1", fetchCalls)
814816
}
815817
}
816818

@@ -856,10 +858,9 @@ func TestVerifyAttestedDocumentWithRootAndFetcher_FetchesMissingIntermediate(t *
856858
}
857859
}
858860

859-
// TestVerifyAttestedDocumentWithRootAndFetcher_CombinesEmbeddedAndFetchErrors
860-
// verifies that when the embedded chain fails AND the fetcher also fails, the
861-
// returned error wraps both so callers can inspect either cause.
862-
func TestVerifyAttestedDocumentWithRootAndFetcher_CombinesEmbeddedAndFetchErrors(t *testing.T) {
861+
// TestVerifyAttestedDocumentWithRootAndFetcher_PropagatesFetchError verifies
862+
// that when the fetcher fails, the error is propagated to the caller.
863+
func TestVerifyAttestedDocumentWithRootAndFetcher_PropagatesFetchError(t *testing.T) {
863864
rootCert, _, leafCert, leafKey := testPKIChain(t)
864865

865866
body := []byte("test-body")
@@ -884,14 +885,11 @@ func TestVerifyAttestedDocumentWithRootAndFetcher_CombinesEmbeddedAndFetchErrors
884885
return nil, fetchErr
885886
})
886887
if err == nil {
887-
t.Fatal("expected error when both embedded chain and fetch fail")
888+
t.Fatal("expected error when fetch fails")
888889
}
889890
if !errors.Is(err, fetchErr) {
890891
t.Errorf("expected wrapped fetchErr in error tree, got: %v", err)
891892
}
892-
if !strings.Contains(err.Error(), "embedded=") || !strings.Contains(err.Error(), "fetch=") {
893-
t.Errorf("expected combined error message to mention both causes, got: %v", err)
894-
}
895893
}
896894

897895
// TestParseAndValidateAttestedDocumentContent_AcceptsMicrosoftFormat verifies

upup/pkg/fi/cloudup/azure/authenticator.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package azure
1919
import (
2020
"fmt"
2121

22+
"k8s.io/klog/v2"
2223
"k8s.io/kops/pkg/bootstrap"
2324
)
2425

@@ -39,6 +40,8 @@ func NewAzureAuthenticator() (bootstrap.Authenticator, error) {
3940
// CreateToken fetches the local VM identity from IMDS and returns a bootstrap
4041
// token containing the resource ID and signed attested document.
4142
func (h *azureAuthenticator) CreateToken(body []byte) (string, error) {
43+
klog.V(2).Infof("Azure authenticator creating bootstrap token")
44+
4245
// Query IMDS for the VM's resource ID.
4346
metadata, err := QueryComputeInstanceMetadata()
4447
if err != nil {
@@ -47,15 +50,19 @@ func (h *azureAuthenticator) CreateToken(body []byte) (string, error) {
4750
if metadata == nil || metadata.ResourceID == "" {
4851
return "", fmt.Errorf("missing resource ID")
4952
}
53+
klog.V(4).Infof("Azure authenticator obtained resource ID %q", metadata.ResourceID)
5054

5155
// Query IMDS for a PKCS7-signed attested document containing the nonce.
52-
doc, err := queryIMDSAttestedDocument(nonceForBody(body))
56+
nonce := nonceForBody(body)
57+
klog.V(4).Infof("Azure authenticator requesting attested document with nonce %q", nonce)
58+
doc, err := queryIMDSAttestedDocument(nonce)
5359
if err != nil {
5460
return "", fmt.Errorf("querying attested document: %w", err)
5561
}
5662
if doc.Signature == "" {
5763
return "", fmt.Errorf("empty attested document signature")
5864
}
65+
klog.V(2).Infof("Azure authenticator obtained attested document for resource %q", metadata.ResourceID)
5966

6067
// Token format: "x-azure-id <resourceID> <base64-pkcs7-signature>"
6168
return AzureAuthenticationTokenPrefix + metadata.ResourceID + " " + doc.Signature, nil

upup/pkg/fi/cloudup/azure/imds.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"net/http"
2424
"net/url"
2525
"time"
26+
27+
"k8s.io/klog/v2"
2628
)
2729

2830
const (
@@ -75,6 +77,8 @@ func queryIMDS(path string, params url.Values, result any) error {
7577
params.Set("api-version", imdsAPIVersion)
7678
req.URL.RawQuery = params.Encode()
7779

80+
klog.V(4).Infof("Querying Azure IMDS: %s (api-version=%s)", path, imdsAPIVersion)
81+
7882
resp, err := imdsHTTPClient.Do(req)
7983
if err != nil {
8084
return fmt.Errorf("querying IMDS %s: %w", path, err)
@@ -89,6 +93,7 @@ func queryIMDS(path string, params url.Values, result any) error {
8993
if err != nil {
9094
return fmt.Errorf("reading IMDS response: %w", err)
9195
}
96+
klog.V(8).Infof("Azure IMDS response for %s: %d bytes", path, len(body))
9297

9398
if err := json.Unmarshal(body, result); err != nil {
9499
return fmt.Errorf("unmarshalling IMDS response: %w", err)

upup/pkg/fi/cloudup/azure/verifier.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
2929
compute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
3030
network "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
31+
"k8s.io/klog/v2"
3132
"k8s.io/kops/pkg/bootstrap"
3233
"k8s.io/kops/pkg/wellknownports"
3334
)
@@ -82,29 +83,35 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
8283
resourceID := v[0]
8384
signature := v[1]
8485

86+
klog.V(2).Infof("Azure verifier processing token for resource %q", resourceID)
87+
8588
// Parse the resource ID early to reject malformed tokens before expensive crypto.
8689
res, err := arm.ParseResourceID(resourceID)
8790
if err != nil {
8891
return nil, fmt.Errorf("parsing resource ID: %w", err)
8992
}
93+
klog.V(4).Infof("Azure verifier parsed resource ID: type=%q subscription=%q resourceGroup=%q name=%q", res.ResourceType, res.SubscriptionID, res.ResourceGroupName, res.Name)
9094

9195
// Reject resource IDs outside the verifier's own subscription / resource
9296
// group. The Azure API lookup below is already scoped to kops-controller's
9397
// subscription and resource group, so any claim that names a different
9498
// location cannot describe a cluster VM. Failing here avoids a wasted
9599
// Azure API call and makes the scope explicit instead of implicit.
96100
if !strings.EqualFold(res.SubscriptionID, a.client.subscriptionID) {
97-
return nil, fmt.Errorf("resource ID subscription %q does not match verifier subscription", res.SubscriptionID)
101+
return nil, fmt.Errorf("resource ID subscription %q does not match verifier subscription %q", res.SubscriptionID, a.client.subscriptionID)
98102
}
99103
if !strings.EqualFold(res.ResourceGroupName, a.client.resourceGroup) {
100-
return nil, fmt.Errorf("resource ID resource group %q does not match verifier resource group", res.ResourceGroupName)
104+
return nil, fmt.Errorf("resource ID resource group %q does not match verifier resource group %q", res.ResourceGroupName, a.client.resourceGroup)
101105
}
106+
klog.V(4).Infof("Azure verifier confirmed resource ID belongs to subscription %q resource group %q", a.client.subscriptionID, a.client.resourceGroup)
102107

103108
// Verify the PKCS7 attested document: signature, certificate chain, nonce, and expiration.
109+
klog.V(2).Infof("Azure verifier verifying attested document for resource %q", resourceID)
104110
data, err := verifyAttestedDocument(signature, body)
105111
if err != nil {
106112
return nil, err
107113
}
114+
klog.V(2).Infof("Azure verifier attested document verified: vmId=%q subscriptionId=%q", data.VMId, data.SubscriptionId)
108115

109116
// Look up the VM or VMSS VM via the Azure API using the resource ID,
110117
// cross-verify the attested vmId, and extract node identity.
@@ -114,6 +121,7 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
114121
switch res.ResourceType.String() {
115122
case "Microsoft.Compute/virtualMachines":
116123
vmName := res.Name
124+
klog.V(2).Infof("Azure verifier looking up VM %q via Azure API", vmName)
117125

118126
// Fetch the VM from the Azure API.
119127
vm, err := a.client.vmsClient.Get(ctx, a.client.resourceGroup, vmName, nil)
@@ -126,6 +134,7 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
126134

127135
// Cross-verify: the vmId from the cryptographically signed attested document
128136
// must match the vmId from the Azure API for the claimed resource ID.
137+
klog.V(4).Infof("Azure verifier cross-verifying vmId: attested=%q api=%q", data.VMId, *vm.Properties.VMID)
129138
if data.VMId != *vm.Properties.VMID {
130139
return nil, fmt.Errorf("attested vmId %q does not match VM %q (API vmId %q)", data.VMId, vmName, *vm.Properties.VMID)
131140
}
@@ -140,6 +149,7 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
140149
} else {
141150
return nil, fmt.Errorf("determining IG name for VM %q", vmName)
142151
}
152+
klog.V(4).Infof("Azure verifier VM %q identity: node=%q instanceGroup=%q", vmName, nodeName, igName)
143153

144154
// Collect private IP addresses from the VM's network interface.
145155
ni, err := a.client.nisClient.Get(ctx, a.client.resourceGroup, nodeName, nil)
@@ -157,6 +167,7 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
157167
case "Microsoft.Compute/virtualMachineScaleSets/virtualMachines":
158168
vmssName := res.Parent.Name
159169
vmssIndex := res.Name
170+
klog.V(2).Infof("Azure verifier looking up VMSS VM %q #%s via Azure API", vmssName, vmssIndex)
160171

161172
// Verify the VMSS belongs to this cluster.
162173
if !strings.HasSuffix(vmssName, "."+a.clusterName) {
@@ -174,6 +185,7 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
174185

175186
// Cross-verify: the vmId from the cryptographically signed attested document
176187
// must match the vmId from the Azure API for the claimed resource ID.
188+
klog.V(4).Infof("Azure verifier cross-verifying vmId: attested=%q api=%q", data.VMId, *vm.Properties.VMID)
177189
if data.VMId != *vm.Properties.VMID {
178190
return nil, fmt.Errorf("attested vmId %q does not match VMSS %q VM #%s (API vmId %q)", data.VMId, vmssName, vmssIndex, *vm.Properties.VMID)
179191
}
@@ -188,6 +200,7 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
188200
} else {
189201
return nil, fmt.Errorf("determining IG name for VM %q", vmssName)
190202
}
203+
klog.V(4).Infof("Azure verifier VMSS VM %q #%s identity: node=%q instanceGroup=%q", vmssName, vmssIndex, nodeName, igName)
191204

192205
// Collect private IP addresses from the VMSS VM's network interface.
193206
ni, err := a.client.nisClient.GetVirtualMachineScaleSetNetworkInterface(ctx, a.client.resourceGroup, vmssName, vmssIndex, vmssName, nil)
@@ -221,6 +234,7 @@ func (a azureVerifier) VerifyToken(ctx context.Context, rawRequest *http.Request
221234
ChallengeEndpoint: challengeEndpoints[0],
222235
}
223236

237+
klog.V(2).Infof("Azure verifier verified node %q in instance group %q with %d address(es)", nodeName, igName, len(addrs))
224238
return result, nil
225239
}
226240

@@ -246,6 +260,7 @@ func newVerifierClient() (*client, error) {
246260
if metadata.SubscriptionID == "" {
247261
return nil, fmt.Errorf("empty subscription ID")
248262
}
263+
klog.V(4).Infof("Azure verifier client using subscription %q resource group %q", metadata.SubscriptionID, metadata.ResourceGroupName)
249264

250265
cred, err := azidentity.NewDefaultAzureCredential(nil)
251266
if err != nil {

0 commit comments

Comments
 (0)