154 lines
4.5 KiB
Go
154 lines
4.5 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/base64"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
|
||
|
|
"github.com/oracle/oci-go-sdk/v65/common"
|
||
|
|
"github.com/oracle/oci-go-sdk/v65/common/auth"
|
||
|
|
"github.com/oracle/oci-go-sdk/v65/objectstorage"
|
||
|
|
"github.com/oracle/oci-go-sdk/v65/secrets"
|
||
|
|
"github.com/oracle/oci-go-sdk/v65/vault"
|
||
|
|
)
|
||
|
|
|
||
|
|
const maxAdmissionBytes = 64 << 20
|
||
|
|
const maxSMTPDenyBytes = 16 << 20
|
||
|
|
|
||
|
|
type ociClients struct {
|
||
|
|
objects objectstorage.ObjectStorageClient
|
||
|
|
secrets secrets.SecretsClient
|
||
|
|
vault vault.VaultsClient
|
||
|
|
}
|
||
|
|
|
||
|
|
func newOCIClients() (ociClients, error) {
|
||
|
|
provider, err := auth.InstancePrincipalConfigurationProvider()
|
||
|
|
if err != nil {
|
||
|
|
return ociClients{}, err
|
||
|
|
}
|
||
|
|
objectClient, err := objectstorage.NewObjectStorageClientWithConfigurationProvider(provider)
|
||
|
|
if err != nil {
|
||
|
|
return ociClients{}, err
|
||
|
|
}
|
||
|
|
secretClient, err := secrets.NewSecretsClientWithConfigurationProvider(provider)
|
||
|
|
if err != nil {
|
||
|
|
return ociClients{}, err
|
||
|
|
}
|
||
|
|
vaultClient, err := vault.NewVaultsClientWithConfigurationProvider(provider)
|
||
|
|
if err != nil {
|
||
|
|
return ociClients{}, err
|
||
|
|
}
|
||
|
|
return ociClients{objects: objectClient, secrets: secretClient, vault: vaultClient}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (clients ociClients) getSecret(ctx context.Context, secretOCID string) ([]byte, error) {
|
||
|
|
response, err := clients.secrets.GetSecretBundle(ctx, secrets.GetSecretBundleRequest{
|
||
|
|
SecretId: common.String(secretOCID),
|
||
|
|
Stage: secrets.GetSecretBundleStageCurrent,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
var encoded *string
|
||
|
|
switch content := response.SecretBundleContent.(type) {
|
||
|
|
case secrets.Base64SecretBundleContentDetails:
|
||
|
|
encoded = content.Content
|
||
|
|
case *secrets.Base64SecretBundleContentDetails:
|
||
|
|
encoded = content.Content
|
||
|
|
}
|
||
|
|
if encoded == nil {
|
||
|
|
return nil, errors.New("secret has no base64 content")
|
||
|
|
}
|
||
|
|
decoded, err := base64.StdEncoding.DecodeString(*encoded)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("decode secret: %w", err)
|
||
|
|
}
|
||
|
|
return decoded, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (clients ociClients) publishSecret(ctx context.Context, secretOCID string, value []byte) error {
|
||
|
|
current, err := clients.getSecret(ctx, secretOCID)
|
||
|
|
if err == nil && string(current) == string(value) {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
content := base64.StdEncoding.EncodeToString(value)
|
||
|
|
_, err = clients.vault.UpdateSecret(ctx, vault.UpdateSecretRequest{
|
||
|
|
SecretId: common.String(secretOCID),
|
||
|
|
UpdateSecretDetails: vault.UpdateSecretDetails{
|
||
|
|
SecretContent: vault.Base64SecretContentDetails{
|
||
|
|
Content: common.String(content),
|
||
|
|
Stage: vault.SecretContentDetailsStageCurrent,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (clients ociClients) downloadAdmission(ctx context.Context, configuration config) (string, func(), error) {
|
||
|
|
return clients.downloadObject(
|
||
|
|
ctx,
|
||
|
|
webAllowStateDirectory,
|
||
|
|
configuration.Admission.Namespace,
|
||
|
|
configuration.Admission.Bucket,
|
||
|
|
configuration.Admission.Object,
|
||
|
|
maxAdmissionBytes,
|
||
|
|
"web-allow",
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (clients ociClients) downloadSMTPDeny(ctx context.Context, configuration config) (string, func(), error) {
|
||
|
|
if configuration.SMTPDeny == nil {
|
||
|
|
return "", nil, errors.New("SMTP deny configuration is inactive")
|
||
|
|
}
|
||
|
|
return clients.downloadObject(
|
||
|
|
ctx,
|
||
|
|
smtpDenyStateDirectory,
|
||
|
|
configuration.SMTPDeny.Namespace,
|
||
|
|
configuration.SMTPDeny.Bucket,
|
||
|
|
configuration.SMTPDeny.Object,
|
||
|
|
maxSMTPDenyBytes,
|
||
|
|
"smtp-deny",
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (clients ociClients) downloadObject(ctx context.Context, stateDirectory, namespace, bucket, object string, maximumBytes int64, label string) (string, func(), error) {
|
||
|
|
directory, err := os.MkdirTemp(stateDirectory, ".download-")
|
||
|
|
if err != nil {
|
||
|
|
return "", nil, err
|
||
|
|
}
|
||
|
|
cleanup := func() { _ = os.RemoveAll(directory) }
|
||
|
|
response, err := clients.objects.GetObject(ctx, objectstorage.GetObjectRequest{
|
||
|
|
NamespaceName: common.String(namespace),
|
||
|
|
BucketName: common.String(bucket),
|
||
|
|
ObjectName: common.String(object),
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
cleanup()
|
||
|
|
return "", nil, err
|
||
|
|
}
|
||
|
|
defer func() { _ = response.Content.Close() }()
|
||
|
|
path := filepath.Join(directory, "bundle.json.gz")
|
||
|
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
|
||
|
|
if err != nil {
|
||
|
|
cleanup()
|
||
|
|
return "", nil, err
|
||
|
|
}
|
||
|
|
written, copyErr := io.Copy(file, io.LimitReader(response.Content, maximumBytes+1))
|
||
|
|
closeErr := file.Close()
|
||
|
|
if copyErr != nil || closeErr != nil || written > maximumBytes {
|
||
|
|
cleanup()
|
||
|
|
return "", nil, errors.Join(copyErr, closeErr, sizeError(label, written, maximumBytes))
|
||
|
|
}
|
||
|
|
return path, cleanup, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func sizeError(label string, size, maximum int64) error {
|
||
|
|
if size > maximum {
|
||
|
|
return fmt.Errorf("%s bundle exceeds %d bytes", label, maximum)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|