Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions sdk/security/keyvault/azcertificates/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
# Release History

## 1.5.0 (Unreleased)

## 1.5.0 (2026-03-22)
### Features Added
* Added support for IP addresses and URIs in `SubjectAlternativeNames` through new `IPAddresses` and `URIs` fields

### Breaking Changes

### Bugs Fixed
- New field `IPAddresses`, `URIs` in struct `SubjectAlternativeNames`

Comment on lines +3 to 7
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 1.5.0 entry no longer mentions the API/service version bump to 2025-07-01 that this generation PR is based on, even though prior entries consistently call out service version upgrades in the changelog. Please add an Other Changes (or similar) bullet noting the service/API version upgrade so the release notes remain complete and consistent.

Copilot uses AI. Check for mistakes.
### Other Changes
* Upgraded to API service version `2025-07-01`

- Upgraded to API service version `2025-07-01`


## 1.4.0 (2025-06-12)

Expand Down
2 changes: 1 addition & 1 deletion sdk/security/keyvault/azcertificates/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "go",
"TagPrefix": "go/security/keyvault/azcertificates",
"Tag": "go/security/keyvault/azcertificates_d7cf8d07c6"
"Tag": "go/security/keyvault/azcertificates_a449e573ec"
}
6 changes: 0 additions & 6 deletions sdk/security/keyvault/azcertificates/build.go

This file was deleted.

190 changes: 190 additions & 0 deletions sdk/security/keyvault/azcertificates/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,3 +735,193 @@ func TestSubjectAlternativeNames(t *testing.T) {
require.NotNil(t, getResp.Policy.X509CertificateProperties.SubjectAlternativeNames)
testSerde(t, getResp.Policy.X509CertificateProperties.SubjectAlternativeNames)
}

func TestSubjectAlternativeNamesSerde(t *testing.T) {
// Test serialization round-trip with all SAN fields including IPAddresses and URIs
t.Run("AllFields", func(t *testing.T) {
san := azcertificates.SubjectAlternativeNames{
DNSNames: []*string{to.Ptr("localhost"), to.Ptr("example.com")},
Emails: []*string{to.Ptr("admin@example.com")},
IPAddresses: []*string{to.Ptr("192.168.1.1"), to.Ptr("2001:0db8::1")},
URIs: []*string{to.Ptr("https://example.com"), to.Ptr("https://test.com/path")},
UserPrincipalNames: []*string{to.Ptr("user@domain.com")},
}
data, err := san.MarshalJSON()
require.NoError(t, err)

// verify JSON keys
var raw map[string]json.RawMessage
require.NoError(t, json.Unmarshal(data, &raw))
require.Contains(t, raw, "dns_names")
require.Contains(t, raw, "emails")
require.Contains(t, raw, "ipAddresses")
require.Contains(t, raw, "uris")
require.Contains(t, raw, "upns")

// round-trip
var san2 azcertificates.SubjectAlternativeNames
require.NoError(t, san2.UnmarshalJSON(data))
require.Equal(t, san.DNSNames, san2.DNSNames)
require.Equal(t, san.Emails, san2.Emails)
require.Equal(t, san.IPAddresses, san2.IPAddresses)
require.Equal(t, san.URIs, san2.URIs)
require.Equal(t, san.UserPrincipalNames, san2.UserPrincipalNames)
})

// Test with only the new IPAddresses field
t.Run("OnlyIPAddresses", func(t *testing.T) {
san := azcertificates.SubjectAlternativeNames{
IPAddresses: []*string{to.Ptr("10.0.0.1"), to.Ptr("fe80::1")},
}
data, err := san.MarshalJSON()
require.NoError(t, err)

var raw map[string]json.RawMessage
require.NoError(t, json.Unmarshal(data, &raw))
require.Contains(t, raw, "ipAddresses")
require.NotContains(t, raw, "dns_names")
require.NotContains(t, raw, "uris")

var san2 azcertificates.SubjectAlternativeNames
require.NoError(t, san2.UnmarshalJSON(data))
require.Equal(t, san.IPAddresses, san2.IPAddresses)
require.Nil(t, san2.DNSNames)
require.Nil(t, san2.URIs)
})

// Test with only the new URIs field
t.Run("OnlyURIs", func(t *testing.T) {
san := azcertificates.SubjectAlternativeNames{
URIs: []*string{to.Ptr("https://example.com"), to.Ptr("spiffe://cluster.local/ns/default/sa/app")},
}
data, err := san.MarshalJSON()
require.NoError(t, err)

var raw map[string]json.RawMessage
require.NoError(t, json.Unmarshal(data, &raw))
require.Contains(t, raw, "uris")
require.NotContains(t, raw, "ipAddresses")

var san2 azcertificates.SubjectAlternativeNames
require.NoError(t, san2.UnmarshalJSON(data))
require.Equal(t, san.URIs, san2.URIs)
require.Nil(t, san2.IPAddresses)
})

// Test deserialization from raw JSON matching the Key Vault REST API format
t.Run("DeserializeFromJSON", func(t *testing.T) {
jsonData := []byte(`{
"dns_names": ["host.example.com"],
"emails": ["test@example.com"],
"ipAddresses": ["192.168.0.1", "::1"],
"uris": ["https://api.example.com"],
"upns": ["admin@contoso.com"]
}`)

var san azcertificates.SubjectAlternativeNames
require.NoError(t, san.UnmarshalJSON(jsonData))
require.Equal(t, []*string{to.Ptr("host.example.com")}, san.DNSNames)
require.Equal(t, []*string{to.Ptr("test@example.com")}, san.Emails)
require.Equal(t, []*string{to.Ptr("192.168.0.1"), to.Ptr("::1")}, san.IPAddresses)
require.Equal(t, []*string{to.Ptr("https://api.example.com")}, san.URIs)
require.Equal(t, []*string{to.Ptr("admin@contoso.com")}, san.UserPrincipalNames)
})

// Test that empty SANs produce an empty JSON object
t.Run("EmptySAN", func(t *testing.T) {
san := azcertificates.SubjectAlternativeNames{}
data, err := san.MarshalJSON()
require.NoError(t, err)
require.Equal(t, "{}", string(data))

var san2 azcertificates.SubjectAlternativeNames
require.NoError(t, san2.UnmarshalJSON(data))
require.Nil(t, san2.IPAddresses)
require.Nil(t, san2.URIs)
})

// Test complete CreateCertificateParameters serde with new SAN fields
t.Run("CreateParamsWithSANs", func(t *testing.T) {
params := azcertificates.CreateCertificateParameters{
CertificatePolicy: &azcertificates.CertificatePolicy{
IssuerParameters: &azcertificates.IssuerParameters{Name: to.Ptr("Self")},
X509CertificateProperties: &azcertificates.X509CertificateProperties{
Subject: to.Ptr("CN=TestSAN"),
SubjectAlternativeNames: &azcertificates.SubjectAlternativeNames{
DNSNames: []*string{to.Ptr("localhost")},
IPAddresses: []*string{to.Ptr("127.0.0.1"), to.Ptr("::1")},
URIs: []*string{to.Ptr("https://localhost:8443")},
},
},
},
}
testSerde(t, &params)
})
}

func TestSubjectAlternativeNamesFakeServer(t *testing.T) {
// Test using the fake server to verify SANs round-trip through create and get policy
expectedSANs := &azcertificates.SubjectAlternativeNames{
DNSNames: []*string{to.Ptr("example.com")},
Emails: []*string{to.Ptr("admin@example.com")},
IPAddresses: []*string{to.Ptr("10.0.0.1"), to.Ptr("2001:db8::1")},
URIs: []*string{to.Ptr("https://example.com/api")},
UserPrincipalNames: []*string{to.Ptr("user@example.com")},
}

srv, close := mock.NewServer(mock.WithTransformAllRequestsToTestServerUrl())
defer close()

// Mock CreateCertificate response
srv.AppendResponse(mock.WithStatusCode(http.StatusAccepted), mock.WithBody([]byte(`{
"id": "https://fake-vault.vault.azure.net/certificates/test-san/pending",
"status": "completed"
}`)))

// Mock GetCertificatePolicy response with all SAN fields
srv.AppendResponse(mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(`{
"id": "https://fake-vault.vault.azure.net/certificates/test-san/policy",
"issuer": {"name": "Self"},
"x509_props": {
"subject": "CN=TestSAN",
"sans": {
"dns_names": ["example.com"],
"emails": ["admin@example.com"],
"ipAddresses": ["10.0.0.1", "2001:db8::1"],
"uris": ["https://example.com/api"],
"upns": ["user@example.com"]
}
}
}`)))

client, err := azcertificates.NewClient("https://fake-vault.vault.azure.net", &azcred.Fake{}, &azcertificates.ClientOptions{
ClientOptions: azcore.ClientOptions{Transport: srv},
})
require.NoError(t, err)

// Create certificate with SANs
createParams := azcertificates.CreateCertificateParameters{
CertificatePolicy: &azcertificates.CertificatePolicy{
IssuerParameters: &azcertificates.IssuerParameters{Name: to.Ptr("Self")},
X509CertificateProperties: &azcertificates.X509CertificateProperties{
Subject: to.Ptr("CN=TestSAN"),
SubjectAlternativeNames: expectedSANs,
},
},
}
_, err = client.CreateCertificate(context.Background(), "test-san", createParams, nil)
require.NoError(t, err)

// Get policy and verify SANs are deserialized correctly
policyResp, err := client.GetCertificatePolicy(context.Background(), "test-san", nil)
require.NoError(t, err)
require.NotNil(t, policyResp.X509CertificateProperties)
require.NotNil(t, policyResp.X509CertificateProperties.SubjectAlternativeNames)

sans := policyResp.X509CertificateProperties.SubjectAlternativeNames
require.Equal(t, expectedSANs.DNSNames, sans.DNSNames)
require.Equal(t, expectedSANs.Emails, sans.Emails)
require.Equal(t, expectedSANs.IPAddresses, sans.IPAddresses)
require.Equal(t, expectedSANs.URIs, sans.URIs)
require.Equal(t, expectedSANs.UserPrincipalNames, sans.UserPrincipalNames)
}
10 changes: 5 additions & 5 deletions sdk/security/keyvault/azcertificates/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates
go 1.24.0

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0
Expand All @@ -18,9 +18,9 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 10 additions & 10 deletions sdk/security/keyvault/azcertificates/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
Expand Down Expand Up @@ -34,15 +34,15 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
Loading