Skip to content

Commit 54e5cc7

Browse files
authored
Add TSIG key support for OpenStack DNS v2 API
Implement TSIG (Transaction SIGnature) key management for the Designate DNS service, enabling authentication of DNS transactions between servers for zone transfers and dynamic updates. Fixes #3622 Signed-off-by: Omer <omersch381@gmail.com>
1 parent 08610c8 commit 54e5cc7

9 files changed

Lines changed: 788 additions & 0 deletions

File tree

internal/acceptance/openstack/dns/v2/dns.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/gophercloud/gophercloud/v2/openstack/dns/v2/recordsets"
1010
transferAccepts "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/transfer/accept"
1111
transferRequests "github.com/gophercloud/gophercloud/v2/openstack/dns/v2/transfer/request"
12+
"github.com/gophercloud/gophercloud/v2/openstack/dns/v2/tsigkeys"
1213
"github.com/gophercloud/gophercloud/v2/openstack/dns/v2/zones"
1314
th "github.com/gophercloud/gophercloud/v2/testhelper"
1415
)
@@ -308,3 +309,48 @@ func WaitForZoneStatus(client *gophercloud.ServiceClient, zone *zones.Zone, stat
308309
return false, nil
309310
})
310311
}
312+
313+
// CreateTSIGKey will create a TSIG key with a random name. An error will
314+
// be returned if the TSIG key was unable to be created.
315+
func CreateTSIGKey(t *testing.T, client *gophercloud.ServiceClient) (*tsigkeys.TSIGKey, error) {
316+
keyName := tools.RandomString("ACPTTEST", 8)
317+
318+
t.Logf("Attempting to create TSIG key: %s", keyName)
319+
createOpts := tsigkeys.CreateOpts{
320+
Name: keyName,
321+
Algorithm: "hmac-sha256",
322+
Secret: "example-test-secret-key==",
323+
Scope: "POOL",
324+
// Default pool ID from designate/conf/central.py
325+
ResourceID: "794ccc2c-d751-44fe-b57f-8894c9f5c842",
326+
}
327+
328+
tsigkey, err := tsigkeys.Create(context.TODO(), client, createOpts).Extract()
329+
if err != nil {
330+
return tsigkey, err
331+
}
332+
333+
newTSIGKey, err := tsigkeys.Get(context.TODO(), client, tsigkey.ID).Extract()
334+
if err != nil {
335+
return tsigkey, err
336+
}
337+
338+
t.Logf("Created TSIG key: %s", keyName)
339+
340+
th.AssertEquals(t, newTSIGKey.Name, keyName)
341+
th.AssertEquals(t, newTSIGKey.Algorithm, "hmac-sha256")
342+
343+
return newTSIGKey, nil
344+
}
345+
346+
// DeleteTSIGKey will delete a specified TSIG key. A fatal error will occur if
347+
// the TSIG key failed to be deleted. This works best when used as a deferred
348+
// function.
349+
func DeleteTSIGKey(t *testing.T, client *gophercloud.ServiceClient, tsigkey *tsigkeys.TSIGKey) {
350+
err := tsigkeys.Delete(context.TODO(), client, tsigkey.ID).ExtractErr()
351+
if err != nil {
352+
t.Fatalf("Unable to delete TSIG key %s: %v", tsigkey.ID, err)
353+
}
354+
355+
t.Logf("Deleted TSIG key: %s", tsigkey.ID)
356+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//go:build acceptance || dns || tsigkeys
2+
3+
package v2
4+
5+
import (
6+
"context"
7+
"testing"
8+
9+
"github.com/gophercloud/gophercloud/v2/internal/acceptance/clients"
10+
"github.com/gophercloud/gophercloud/v2/internal/acceptance/tools"
11+
"github.com/gophercloud/gophercloud/v2/openstack/dns/v2/tsigkeys"
12+
th "github.com/gophercloud/gophercloud/v2/testhelper"
13+
)
14+
15+
func TestTSIGKeysCRUD(t *testing.T) {
16+
clients.RequireAdmin(t)
17+
18+
client, err := clients.NewDNSV2Client()
19+
th.AssertNoErr(t, err)
20+
21+
tsigkey, err := CreateTSIGKey(t, client)
22+
th.AssertNoErr(t, err)
23+
defer DeleteTSIGKey(t, client, tsigkey)
24+
25+
tools.PrintResource(t, &tsigkey)
26+
27+
allPages, err := tsigkeys.List(client, nil).AllPages(context.TODO())
28+
th.AssertNoErr(t, err)
29+
30+
allTSIGKeys, err := tsigkeys.ExtractTSIGKeys(allPages)
31+
th.AssertNoErr(t, err)
32+
33+
var found bool
34+
for _, k := range allTSIGKeys {
35+
tools.PrintResource(t, &k)
36+
37+
if tsigkey.Name == k.Name {
38+
found = true
39+
}
40+
}
41+
42+
th.AssertEquals(t, found, true)
43+
44+
updateOpts := tsigkeys.UpdateOpts{
45+
Name: tsigkey.Name + "-updated",
46+
Secret: "updated-test-secret-key==",
47+
}
48+
49+
newTSIGKey, err := tsigkeys.Update(context.TODO(), client, tsigkey.ID, updateOpts).Extract()
50+
th.AssertNoErr(t, err)
51+
52+
tools.PrintResource(t, &newTSIGKey)
53+
54+
th.AssertEquals(t, newTSIGKey.Name, tsigkey.Name+"-updated")
55+
th.AssertEquals(t, newTSIGKey.Secret, "updated-test-secret-key==")
56+
}

openstack/dns/v2/tsigkeys/doc.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Package tsigkeys provides information and interaction with the TSIG key API
3+
resource for the OpenStack DNS service.
4+
5+
TSIG (Transaction SIGnature) keys are used to authenticate DNS transactions
6+
between servers, such as zone transfers and dynamic updates.
7+
8+
Example to List TSIG Keys
9+
10+
listOpts := tsigkeys.ListOpts{
11+
Scope: "POOL",
12+
}
13+
14+
allPages, err := tsigkeys.List(dnsClient, listOpts).AllPages(context.TODO())
15+
if err != nil {
16+
panic(err)
17+
}
18+
19+
allTSIGKeys, err := tsigkeys.ExtractTSIGKeys(allPages)
20+
if err != nil {
21+
panic(err)
22+
}
23+
24+
for _, tsigkey := range allTSIGKeys {
25+
fmt.Printf("%+v\n", tsigkey)
26+
}
27+
28+
Example to Create a TSIG Key
29+
30+
createOpts := tsigkeys.CreateOpts{
31+
Name: "mytsigkey",
32+
Algorithm: "hmac-sha256",
33+
Secret: "example-secret-key-value==",
34+
Scope: "POOL",
35+
ResourceID: "pool-id-here",
36+
}
37+
38+
tsigkey, err := tsigkeys.Create(context.TODO(), dnsClient, createOpts).Extract()
39+
if err != nil {
40+
panic(err)
41+
}
42+
43+
Example to Get a TSIG Key
44+
45+
tsigkeyID := "99d10f68-5623-4491-91a0-6daafa32b60e"
46+
tsigkey, err := tsigkeys.Get(context.TODO(), dnsClient, tsigkeyID).Extract()
47+
if err != nil {
48+
panic(err)
49+
}
50+
51+
Example to Update a TSIG Key
52+
53+
tsigkeyID := "99d10f68-5623-4491-91a0-6daafa32b60e"
54+
updateOpts := tsigkeys.UpdateOpts{
55+
Name: "updatedname",
56+
Secret: "updated-secret-key-value==",
57+
}
58+
59+
tsigkey, err := tsigkeys.Update(context.TODO(), dnsClient, tsigkeyID, updateOpts).Extract()
60+
if err != nil {
61+
panic(err)
62+
}
63+
64+
Example to Delete a TSIG Key
65+
66+
tsigkeyID := "99d10f68-5623-4491-91a0-6daafa32b60e"
67+
err := tsigkeys.Delete(context.TODO(), dnsClient, tsigkeyID).ExtractErr()
68+
if err != nil {
69+
panic(err)
70+
}
71+
*/
72+
package tsigkeys
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package tsigkeys
2+
3+
import (
4+
"context"
5+
6+
"github.com/gophercloud/gophercloud/v2"
7+
"github.com/gophercloud/gophercloud/v2/pagination"
8+
)
9+
10+
// ListOptsBuilder allows extensions to add parameters to the List request.
11+
type ListOptsBuilder interface {
12+
ToTSIGKeyListQuery() (string, error)
13+
}
14+
15+
// ListOpts allows the filtering and sorting of paginated collections through
16+
// the API. Filtering is achieved by passing in struct field values that map to
17+
// the server attributes you want to see returned. Marker and Limit are used
18+
// for pagination.
19+
// https://docs.openstack.org/api-ref/dns/
20+
type ListOpts struct {
21+
// Integer value for the limit of values to return.
22+
Limit int `q:"limit"`
23+
24+
// UUID of the TSIG key at which you want to set a marker.
25+
Marker string `q:"marker"`
26+
27+
// Name of the TSIG key.
28+
Name string `q:"name"`
29+
30+
// Algorithm used by the TSIG key.
31+
Algorithm string `q:"algorithm"`
32+
33+
// Scope of the TSIG key (ZONE or POOL).
34+
Scope string `q:"scope"`
35+
}
36+
37+
// ToTSIGKeyListQuery formats a ListOpts into a query string.
38+
func (opts ListOpts) ToTSIGKeyListQuery() (string, error) {
39+
q, err := gophercloud.BuildQueryString(opts)
40+
return q.String(), err
41+
}
42+
43+
// List implements a TSIG key List request.
44+
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
45+
url := baseURL(client)
46+
if opts != nil {
47+
query, err := opts.ToTSIGKeyListQuery()
48+
if err != nil {
49+
return pagination.Pager{Err: err}
50+
}
51+
url += query
52+
}
53+
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
54+
return TSIGKeyPage{pagination.LinkedPageBase{PageResult: r}}
55+
})
56+
}
57+
58+
// Get returns information about a TSIG key, given its ID.
59+
func Get(ctx context.Context, client *gophercloud.ServiceClient, tsigkeyID string) (r GetResult) {
60+
resp, err := client.Get(ctx, tsigkeyURL(client, tsigkeyID), &r.Body, nil)
61+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
62+
return
63+
}
64+
65+
// CreateOptsBuilder allows extensions to add additional attributes to the
66+
// Create request.
67+
type CreateOptsBuilder interface {
68+
ToTSIGKeyCreateMap() (map[string]any, error)
69+
}
70+
71+
// CreateOpts specifies the attributes used to create a TSIG key.
72+
type CreateOpts struct {
73+
// Name of the TSIG key.
74+
Name string `json:"name" required:"true"`
75+
76+
// Algorithm is the TSIG algorithm (e.g., hmac-sha256, hmac-sha512).
77+
Algorithm string `json:"algorithm" required:"true"`
78+
79+
// Secret is the base64-encoded secret key.
80+
Secret string `json:"secret" required:"true"`
81+
82+
// Scope defines the scope of the TSIG key (ZONE or POOL).
83+
Scope string `json:"scope" required:"true"`
84+
85+
// ResourceID is the ID of the resource (zone or pool) this key is associated with.
86+
ResourceID string `json:"resource_id" required:"true"`
87+
}
88+
89+
// ToTSIGKeyCreateMap formats a CreateOpts structure into a request body.
90+
func (opts CreateOpts) ToTSIGKeyCreateMap() (map[string]any, error) {
91+
return gophercloud.BuildRequestBody(opts, "")
92+
}
93+
94+
// Create implements a TSIG key create request.
95+
func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
96+
b, err := opts.ToTSIGKeyCreateMap()
97+
if err != nil {
98+
r.Err = err
99+
return
100+
}
101+
resp, err := client.Post(ctx, baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{
102+
OkCodes: []int{201},
103+
})
104+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
105+
return
106+
}
107+
108+
// UpdateOptsBuilder allows extensions to add additional attributes to the
109+
// Update request.
110+
type UpdateOptsBuilder interface {
111+
ToTSIGKeyUpdateMap() (map[string]any, error)
112+
}
113+
114+
// UpdateOpts specifies the attributes to update a TSIG key.
115+
type UpdateOpts struct {
116+
// Name of the TSIG key.
117+
Name string `json:"name,omitempty"`
118+
119+
// Algorithm is the TSIG algorithm.
120+
Algorithm string `json:"algorithm,omitempty"`
121+
122+
// Secret is the base64-encoded secret key.
123+
Secret string `json:"secret,omitempty"`
124+
125+
// Scope defines the scope of the TSIG key.
126+
Scope string `json:"scope,omitempty"`
127+
128+
// ResourceID is the ID of the resource this key is associated with.
129+
ResourceID string `json:"resource_id,omitempty"`
130+
}
131+
132+
// ToTSIGKeyUpdateMap formats an UpdateOpts structure into a request body.
133+
func (opts UpdateOpts) ToTSIGKeyUpdateMap() (map[string]any, error) {
134+
return gophercloud.BuildRequestBody(opts, "")
135+
}
136+
137+
// Update implements a TSIG key update request.
138+
func Update(ctx context.Context, client *gophercloud.ServiceClient, tsigkeyID string, opts UpdateOptsBuilder) (r UpdateResult) {
139+
b, err := opts.ToTSIGKeyUpdateMap()
140+
if err != nil {
141+
r.Err = err
142+
return
143+
}
144+
resp, err := client.Patch(ctx, tsigkeyURL(client, tsigkeyID), &b, &r.Body, &gophercloud.RequestOpts{
145+
OkCodes: []int{200},
146+
})
147+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
148+
return
149+
}
150+
151+
// Delete implements a TSIG key delete request.
152+
func Delete(ctx context.Context, client *gophercloud.ServiceClient, tsigkeyID string) (r DeleteResult) {
153+
resp, err := client.Delete(ctx, tsigkeyURL(client, tsigkeyID), &gophercloud.RequestOpts{
154+
OkCodes: []int{204},
155+
})
156+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
157+
return
158+
}

0 commit comments

Comments
 (0)