Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APIGW]: certificate endpoints #782

Merged
merged 2 commits into from
Jan 15, 2025
Merged
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
153 changes: 153 additions & 0 deletions acceptance/openstack/apigw/v2/cert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package v2

import (
"os"
"testing"

golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
"github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients"
"github.com/opentelekomcloud/gophertelekomcloud/acceptance/openstack"
"github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools"
"github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/cert"
"github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/domain"
"github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/group"
"github.com/opentelekomcloud/gophertelekomcloud/openstack/dns/v2/zones"
th "github.com/opentelekomcloud/gophertelekomcloud/testhelper"
)

func TestCertificateLifecycle(t *testing.T) {
gatewayID := os.Getenv("GATEWAY_ID")
if gatewayID == "" {
t.Skip("`GATEWAY_ID` needs to be defined")
}

client, err := clients.NewAPIGWClient()
th.AssertNoErr(t, err)

t.Logf("Attempting to create API Gateway Group")
grp := CreateGroup(client, t, gatewayID)
t.Cleanup(func() {
t.Logf("Attempting to delete API Gateway Group")
th.AssertNoErr(t, group.Delete(client, gatewayID, grp.ID))
})

t.Logf("Attempting to create Public DNS zone with A record")
clientNetwork, err := clients.NewDNSV2Client()
th.AssertNoErr(t, err)
rs := CreateDns(clientNetwork, t)
t.Cleanup(func() {
t.Logf("Attempting to delete Public DNS zone")
_, err := zones.Delete(clientNetwork, rs.ZoneID).Extract()
th.AssertNoErr(t, err)
})

createOpts := domain.CreateOpts{
GatewayID: gatewayID,
GroupID: grp.ID,
UrlDomain: rs.Name,
}
t.Logf("Attempting to create API Gateway Domain")
dom, err := domain.Create(client, createOpts)
th.AssertNoErr(t, err)
t.Cleanup(func() {
t.Logf("Attempting to delete API Gateway Domain")
th.AssertNoErr(t, domain.Delete(client, domain.DeleteOpts{
GatewayID: gatewayID,
GroupID: grp.ID,
DomainID: dom.ID,
}))
})

createResp := CreateTestCertificate(client, t, gatewayID, dom.UrlDomain)

t.Cleanup(func() {
t.Logf("Attempting to delete certificate: %s", createResp.ID)
th.AssertNoErr(t, cert.Delete(client, createResp.ID))
})

newCert, newPk, _ := openstack.GenerateTestCertKeyPair(dom.UrlDomain)

updateOpts := cert.UpdateOpts{
Name: createResp.Name + "_updated",
CertContent: newCert,
PrivateKey: newPk,
Type: "instance",
InstanceID: gatewayID,
}

t.Logf("Attempting to update certificate: %s", createResp.ID)
updateResp, err := cert.Update(client, createResp.ID, updateOpts)
th.AssertNoErr(t, err)
th.AssertEquals(t, updateOpts.Name, updateResp.Name)

t.Logf("Attempting to get certificate: %s", createResp.ID)
getResp, err := cert.Get(client, createResp.ID)
th.AssertNoErr(t, err)
tools.PrintResource(t, getResp)

bindOpts := cert.BindOpts{
InstanceID: gatewayID,
GroupID: grp.ID,
DomainID: dom.ID,
CertificateIDs: []string{
createResp.ID,
},
}

t.Logf("Attempting to bind domain with certificates: %s", createResp.ID)
err = cert.Bind(client, bindOpts)
th.AssertNoErr(t, err)

t.Logf("Attempting to unbind domain certificate: %s", createResp.ID)
err = cert.Unbind(client, bindOpts)
th.AssertNoErr(t, err)

bindCertOpts := cert.AttachDomainOpts{
CertificateID: createResp.ID,
Domains: []cert.AttachDomainInfo{
{
Domain: dom.UrlDomain,
},
},
}

t.Logf("Attempting to bind cert to domain: %s", createResp.ID)
err = cert.BindCertToDomain(client, bindCertOpts)
th.AssertNoErr(t, err)

t.Logf("Attempting to unbind cert from domain: %s", createResp.ID)
err = cert.UnbindCertFromDomain(client, bindCertOpts)
th.AssertNoErr(t, err)
}

func TestCertificateList(t *testing.T) {
client, err := clients.NewAPIGWClient()
gatewayID := os.Getenv("GATEWAY_ID")
if gatewayID == "" {
t.Skip("`GATEWAY_ID` needs to be defined")
}
th.AssertNoErr(t, err)
t.Log("Attempting to list certificates")
allPages, err := cert.List(client, cert.ListOpts{
InstanceId: gatewayID,
})
th.AssertNoErr(t, err)
tools.PrintResource(t, allPages)
}

func CreateTestCertificate(client *golangsdk.ServiceClient, t *testing.T, gatewayID, domainName string) *cert.CertificateResp {
certificate, privateKey, _ := openstack.GenerateTestCertKeyPair(domainName)

opts := cert.CreateOpts{
Name: tools.RandomString("cert_", 5),
CertContent: certificate,
PrivateKey: privateKey,
Type: "instance",
InstanceID: gatewayID,
}

t.Logf("Attempting to create certificate: %s", opts.Name)
createResp, err := cert.Create(client, opts)
th.AssertNoErr(t, err)
return createResp
}
55 changes: 55 additions & 0 deletions acceptance/openstack/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
package openstack

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"testing"
"time"

"github.com/opentelekomcloud/gophertelekomcloud"
"github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients"
Expand Down Expand Up @@ -290,3 +298,50 @@ func CreateServer(t *testing.T, client *golangsdk.ServiceClient, ecsName, imageN

return server
}

// GenerateTestCertKeyPair generates a test certificate and private key pair
func GenerateTestCertKeyPair(domain string) (string, string, error) {
pk, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", "", err
}

template := x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Organization: []string{"Test Organization"},
CommonName: domain,
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0), // Valid for 1 year
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{domain},
}

certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &pk.PublicKey, pk)
if err != nil {
return "", "", err
}

certBuffer := new(bytes.Buffer)
err = pem.Encode(certBuffer, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
if err != nil {
return "", "", err
}

keyBuffer := new(bytes.Buffer)
err = pem.Encode(keyBuffer, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(pk),
})
if err != nil {
return "", "", err
}

return certBuffer.String(), keyBuffer.String(), nil
}
35 changes: 35 additions & 0 deletions openstack/apigw/v2/cert/Bind.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cert

import (
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
"github.com/opentelekomcloud/gophertelekomcloud/internal/build"
)

// BindOpts contains the options for binding SSL certificates to a domain
type BindOpts struct {
// Gateway ID
InstanceID string `json:"-"`
// API group ID
GroupID string `json:"-"`
// Domain ID
DomainID string `json:"-"`
// Certificate IDs to attach
CertificateIDs []string `json:"certificate_ids" required:"true"`
// Whether to enable client certificate verification
VerifiedClientCertificateEnabled *bool `json:"verified_client_certificate_enabled,omitempty"`
}

// Bind SSL certificates to a domain
func Bind(client *golangsdk.ServiceClient, opts BindOpts) error {
b, err := build.RequestBody(opts, "")
if err != nil {
return err
}

// Build URL: /v2/{project_id}/apigw/instances/{instance_id}/api-groups/{group_id}/domains/{domain_id}/certificates/attach
_, err = client.Post(client.ServiceURL("apigw", "instances", opts.InstanceID,
"api-groups", opts.GroupID, "domains", opts.DomainID, "certificates", "attach"), b, nil, &golangsdk.RequestOpts{
OkCodes: []int{200, 204},
})
return err
}
36 changes: 36 additions & 0 deletions openstack/apigw/v2/cert/BindCertToDomain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cert

import (
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
"github.com/opentelekomcloud/gophertelekomcloud/internal/build"
)

// AttachDomainInfo represents the information for a domain to attach
type AttachDomainInfo struct {
// Domain name
Domain string `json:"domain" required:"true"`
// Gateway IDs
InstanceIDs []string `json:"instance_ids,omitempty"`
// Whether to enable client certificate verification
VerifiedClientCertificateEnabled *bool `json:"verified_client_certificate_enabled,omitempty"`
}

// AttachDomainOpts contains the options for binding a certificate to domain names
type AttachDomainOpts struct {
CertificateID string `json:"-"`
// Domain names the certificate is bound to
Domains []AttachDomainInfo `json:"domains" required:"true"`
}

// BindCertToDomain binds an SSL certificate to domain names
func BindCertToDomain(client *golangsdk.ServiceClient, opts AttachDomainOpts) error {
b, err := build.RequestBody(opts, "")
if err != nil {
return err
}

_, err = client.Post(client.ServiceURL("apigw", "certificates", opts.CertificateID, "domains", "attach"), b, nil, &golangsdk.RequestOpts{
OkCodes: []int{200, 204},
})
return err
}
68 changes: 68 additions & 0 deletions openstack/apigw/v2/cert/Create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cert

import (
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
"github.com/opentelekomcloud/gophertelekomcloud/internal/build"
"github.com/opentelekomcloud/gophertelekomcloud/internal/extract"
)

// CreateOpts contains the options for creating a new SSL certificate
type CreateOpts struct {
// Certificate name. It can contain 4 to 50 characters, starting with a letter.
// Only letters, digits, and underscores (_) are allowed.
Name string `json:"name" required:"true"`
// Certificate content
CertContent string `json:"cert_content" required:"true"`
// Certificate private key
PrivateKey string `json:"private_key" required:"true"`
// Certificate scope (instance or global)
Type string `json:"type,omitempty"`
// Gateway ID. Required if type is set to instance
InstanceID string `json:"instance_id,omitempty"`
// Trusted root certificate (CA)
TrustedRootCA string `json:"trusted_root_ca,omitempty"`
}

// Create creates a new SSL certificate
func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*CertificateResp, error) {
b, err := build.RequestBody(opts, "")
if err != nil {
return nil, err
}

raw, err := client.Post(client.ServiceURL("apigw", "certificates"), b, nil, &golangsdk.RequestOpts{
OkCodes: []int{200},
})
if err != nil {
return nil, err
}

var res CertificateResp
err = extract.Into(raw.Body, &res)
return &res, err
}

// CertificateResp represents the response from certificate creation
type CertificateResp struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
InstanceID string `json:"instance_id"`
ProjectID string `json:"project_id"`
CommonName string `json:"common_name"`
San []string `json:"san"`
NotAfter string `json:"not_after"`
NotBefore string `json:"not_before"`
SignatureAlgorithm string `json:"signature_algorithm"`
CreateTime string `json:"create_time"`
UpdateTime string `json:"update_time"`
HasTrustedRootCA bool `json:"is_has_trusted_root_ca"`
Version int `json:"version"`
Organization []string `json:"organization"`
OrganizationalUnit []string `json:"organizational_unit"`
Locality []string `json:"locality"`
State []string `json:"state"`
Country []string `json:"country"`
SerialNumber string `json:"serial_number"`
Issuer []string `json:"issuer"`
}
8 changes: 8 additions & 0 deletions openstack/apigw/v2/cert/Delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package cert

import golangsdk "github.com/opentelekomcloud/gophertelekomcloud"

func Delete(client *golangsdk.ServiceClient, certID string) (err error) {
_, err = client.Delete(client.ServiceURL("apigw", "certificates", certID), nil)
return
}
18 changes: 18 additions & 0 deletions openstack/apigw/v2/cert/Get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cert

import (
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
"github.com/opentelekomcloud/gophertelekomcloud/internal/extract"
)

// Get retrieves details of a specific SSL certificate
func Get(client *golangsdk.ServiceClient, certificateID string) (*CertificateResp, error) {
raw, err := client.Get(client.ServiceURL("apigw", "certificates", certificateID), nil, nil)
if err != nil {
return nil, err
}

var res CertificateResp
err = extract.Into(raw.Body, &res)
return &res, err
}
Loading
Loading