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 }