Skip to content

Commit

Permalink
apigw certs
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-lifshits committed Jan 15, 2025
1 parent 274cd40 commit 2d2fe1b
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 0 deletions.
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, err := openstack.GenerateTestCertKeyPair(dom.UrlDomain)

Check failure on line 68 in acceptance/openstack/apigw/v2/cert_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ineffectual assignment to err (ineffassign)

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, err := openstack.GenerateTestCertKeyPair(domainName)

Check failure on line 139 in acceptance/openstack/apigw/v2/cert_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ineffectual assignment to err (ineffassign)

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

0 comments on commit 2d2fe1b

Please sign in to comment.