Paul Meyer 56beb05170 debugd: implement qemu log collection
Signed-off-by: Paul Meyer <49727155+katexochen@users.noreply.github.com>
2022-11-30 16:26:25 +01:00

254 lines
7.3 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package logcollector
import (
"context"
"errors"
"fmt"
"hash/crc32"
"io"
"strings"
gcpsecretmanager "cloud.google.com/go/secretmanager/apiv1"
gcpsecretmanagerpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
awssecretmanager "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/info"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
gaxv2 "github.com/googleapis/gax-go/v2"
)
// Credentials contains the credentials for an OpenSearch instance.
type credentials struct {
Username string
Password string
}
// credentialGetter is a wrapper around the cloud provider specific credential getters.
type credentialGetter struct {
openseachCredsGetter
}
type openseachCredsGetter interface {
GetOpensearchCredentials(ctx context.Context) (credentials, error)
io.Closer
}
// NewCloudCredentialGetter returns a new CloudCredentialGetter for the given cloud provider.
func newCloudCredentialGetter(ctx context.Context, provider cloudprovider.Provider, infoMap *info.Map) (*credentialGetter, error) {
switch provider {
case cloudprovider.GCP:
getter, err := newGCPCloudCredentialGetter(ctx)
if err != nil {
return nil, fmt.Errorf("creating GCP cloud credential getter: %w", err)
}
return &credentialGetter{getter}, nil
case cloudprovider.Azure:
getter, err := newAzureCloudCredentialGetter()
if err != nil {
return nil, fmt.Errorf("creating Azure cloud credential getter: %w", err)
}
return &credentialGetter{getter}, nil
case cloudprovider.AWS:
getter, err := newAWSCloudCredentialGetter(ctx)
if err != nil {
return nil, fmt.Errorf("creating AWS cloud credential getter: %w", err)
}
return &credentialGetter{getter}, nil
case cloudprovider.QEMU:
getter, err := newQemuCloudCredentialGetter(infoMap)
if err != nil {
return nil, fmt.Errorf("creating QEMU cloud credential getter: %w", err)
}
return &credentialGetter{getter}, nil
default:
return nil, fmt.Errorf("cloud provider not supported")
}
}
type gcpCloudCredentialGetter struct {
secretsAPI gcpSecretManagerAPI
}
func newGCPCloudCredentialGetter(ctx context.Context) (*gcpCloudCredentialGetter, error) {
client, err := gcpsecretmanager.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("creating secretmanager client: %w", err)
}
return &gcpCloudCredentialGetter{secretsAPI: client}, nil
}
func (g *gcpCloudCredentialGetter) GetOpensearchCredentials(ctx context.Context) (credentials, error) {
const secretName = "projects/796962942582/secrets/e2e-logs-OpenSearch-password/versions/1"
const username = "cluster-instance-gcp"
req := &gcpsecretmanagerpb.AccessSecretVersionRequest{Name: secretName}
resp, err := g.secretsAPI.AccessSecretVersion(ctx, req)
if err != nil {
return credentials{}, fmt.Errorf("accessing secret version: %w", err)
}
if resp.Payload == nil || len(resp.Payload.Data) == 0 {
return credentials{}, errors.New("response payload is empty")
}
crc32c := crc32.MakeTable(crc32.Castagnoli)
checksum := int64(crc32.Checksum(resp.Payload.Data, crc32c))
if checksum != *resp.Payload.DataCrc32C {
return credentials{}, errors.New("data corruption of secret detected")
}
return credentials{
Username: username,
Password: string(resp.Payload.Data),
}, nil
}
func (g *gcpCloudCredentialGetter) Close() error {
return g.secretsAPI.Close()
}
type gcpSecretManagerAPI interface {
AccessSecretVersion(ctx context.Context, req *gcpsecretmanagerpb.AccessSecretVersionRequest,
opts ...gaxv2.CallOption,
) (*gcpsecretmanagerpb.AccessSecretVersionResponse, error)
io.Closer
}
type azureCloudCredentialGetter struct {
secretsAPI azureSecretsAPI
}
func newAzureCloudCredentialGetter() (*azureCloudCredentialGetter, error) {
const vaultURI = "https://opensearch-creds.vault.azure.net"
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, fmt.Errorf("creating default azure credential: %w", err)
}
client, err := azsecrets.NewClient(vaultURI, cred, nil)
if err != nil {
return nil, fmt.Errorf("creating Azure secrets client: %w", err)
}
return &azureCloudCredentialGetter{secretsAPI: client}, nil
}
func (a *azureCloudCredentialGetter) GetOpensearchCredentials(ctx context.Context) (credentials, error) {
const secretName = "opensearch-password"
const username = "cluster-instance-azure"
const version = "" // An empty string version gets the latest version of the secret.
resp, err := a.secretsAPI.GetSecret(ctx, secretName, version, nil)
if err != nil {
return credentials{}, fmt.Errorf("getting secret: %w", err)
}
if resp.Value == nil {
return credentials{}, errors.New("response value is empty")
}
return credentials{
Username: username,
Password: *resp.Value,
}, nil
}
func (a *azureCloudCredentialGetter) Close() error {
return nil
}
type azureSecretsAPI interface {
GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions,
) (azsecrets.GetSecretResponse, error)
}
type awsCloudCredentialGetter struct {
secretmanager awsSecretManagerAPI
}
func newAWSCloudCredentialGetter(ctx context.Context) (*awsCloudCredentialGetter, error) {
const region = "eu-central-1"
config, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, fmt.Errorf("loading default AWS config: %w", err)
}
client := awssecretmanager.NewFromConfig(config)
return &awsCloudCredentialGetter{secretmanager: client}, nil
}
func (a *awsCloudCredentialGetter) GetOpensearchCredentials(ctx context.Context) (credentials, error) {
const username = "cluster-instance-aws"
secertName := "opensearch-password"
req := &awssecretmanager.GetSecretValueInput{SecretId: &secertName}
resp, err := a.secretmanager.GetSecretValue(ctx, req)
if err != nil {
return credentials{}, fmt.Errorf("getting secret value: %w", err)
}
if resp.SecretString == nil {
return credentials{}, errors.New("response secret string is empty")
}
password := strings.TrimPrefix(*resp.SecretString, "{\"password\":\"")
password = strings.TrimSuffix(password, "\"}")
return credentials{
Username: username,
Password: password,
}, nil
}
func (a *awsCloudCredentialGetter) Close() error {
return nil
}
type awsSecretManagerAPI interface {
GetSecretValue(ctx context.Context, params *awssecretmanager.GetSecretValueInput,
optFns ...func(*awssecretmanager.Options),
) (*awssecretmanager.GetSecretValueOutput, error)
}
type qemuCloudCredentialGetter struct {
creds credentials
}
func newQemuCloudCredentialGetter(infoMap *info.Map) (*qemuCloudCredentialGetter, error) {
const username = "cluster-instance-qemu"
password, ok, err := infoMap.Get("qemu.opensearch-pw")
if err != nil {
return nil, fmt.Errorf("getting qemu.opensearch-pw from info: %w", err)
}
if !ok {
return nil, errors.New("qemu.opensearch-pw not found in info")
}
return &qemuCloudCredentialGetter{
creds: credentials{
Username: username,
Password: password,
},
}, nil
}
func (q *qemuCloudCredentialGetter) GetOpensearchCredentials(ctx context.Context) (credentials, error) {
return q.creds, nil
}
func (q *qemuCloudCredentialGetter) Close() error {
return nil
}