2026-05-11 22:40:22 -07:00
|
|
|
// Copyright IBM Corp. 2024, 2026
|
2024-10-21 10:47:06 -04:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
|
|
package host
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
compute "cloud.google.com/go/compute/apiv1"
|
|
|
|
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
|
|
|
|
"github.com/googleapis/gax-go/v2"
|
2026-10-12 10:28:05 -07:00
|
|
|
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-gcp/internal/credential"
|
|
|
|
|
pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
|
2024-12-17 14:22:46 -05:00
|
|
|
"google.golang.org/api/iterator"
|
2024-10-21 10:47:06 -04:00
|
|
|
"google.golang.org/api/option"
|
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type InstancesAPI interface {
|
|
|
|
|
List(ctx context.Context, req *computepb.ListInstancesRequest, opts ...gax.CallOption) *compute.InstanceIterator
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type gcpCatalogPersistedState struct {
|
|
|
|
|
// CredentialsConfig is the configuration for the GCP credentials
|
|
|
|
|
*credential.PersistedState
|
|
|
|
|
// testInstancesAPIFunc provides a way to provide a factory for a mock Instances client
|
|
|
|
|
testInstancesAPIFunc instancesAPIFunc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type instancesAPIFunc func(...*credential.Config) (InstancesAPI, error)
|
|
|
|
|
|
|
|
|
|
type gcpCatalogPersistedStateOption func(s *gcpCatalogPersistedState) error
|
|
|
|
|
|
|
|
|
|
func withCredentials(x *credential.PersistedState) gcpCatalogPersistedStateOption {
|
|
|
|
|
return func(s *gcpCatalogPersistedState) error {
|
|
|
|
|
if s.PersistedState != nil {
|
|
|
|
|
return errors.New("gcp credentials already set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.PersistedState = x
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func withTestInstancesAPIFunc(f instancesAPIFunc) gcpCatalogPersistedStateOption {
|
|
|
|
|
return func(s *gcpCatalogPersistedState) error {
|
|
|
|
|
if s.testInstancesAPIFunc != nil {
|
|
|
|
|
return errors.New("test API function already set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.testInstancesAPIFunc = f
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newGCPCatalogPersistedState(opts ...gcpCatalogPersistedStateOption) (*gcpCatalogPersistedState, error) {
|
|
|
|
|
s := new(gcpCatalogPersistedState)
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
if err := opt(s); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return s, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ToProto converts the state to HostCatalogPersisted proto message.
|
|
|
|
|
func (s *gcpCatalogPersistedState) toProto() (*pb.HostCatalogPersisted, error) {
|
|
|
|
|
data, err := structpb.NewStruct(s.ToMap())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error converting state to structpb.Struct: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return &pb.HostCatalogPersisted{Secrets: data}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// InstancesClient returns a configured Instances client based on the session
|
|
|
|
|
// information stored in the state.
|
|
|
|
|
func (s *gcpCatalogPersistedState) InstancesClient(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
opts ...option.ClientOption,
|
|
|
|
|
) (InstancesAPI, error) {
|
|
|
|
|
if s.testInstancesAPIFunc != nil {
|
|
|
|
|
return s.testInstancesAPIFunc(s.CredentialsConfig)
|
|
|
|
|
}
|
|
|
|
|
creds, err := s.CredentialsConfig.GenerateCredentials(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error generation GCP credentials: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if creds == nil {
|
|
|
|
|
return nil, fmt.Errorf("nil gcp credentials")
|
|
|
|
|
}
|
|
|
|
|
clientOptions := []option.ClientOption{option.WithTokenSource(creds.TokenSource)}
|
|
|
|
|
clientOptions = append(clientOptions, opts...)
|
|
|
|
|
|
|
|
|
|
client, err := compute.NewInstancesRESTClient(ctx, clientOptions...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error creating instances client: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return client, nil
|
|
|
|
|
}
|
2024-12-17 14:22:46 -05:00
|
|
|
|
|
|
|
|
// validateCredsCallbackFn returns a ValidateCredsCallback function that
|
|
|
|
|
// validates the credentials by listing instances in the project and zone.
|
|
|
|
|
func (s *gcpCatalogPersistedState) ValidateCredsCallbackFn(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
opts ...option.ClientOption,
|
|
|
|
|
) credential.ValidateCredsCallback {
|
|
|
|
|
return func(config *credential.Config, opts ...option.ClientOption) error {
|
|
|
|
|
s.CredentialsConfig = config
|
|
|
|
|
instancesClient, err := s.InstancesClient(ctx, opts...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error creating instances client: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it := instancesClient.List(ctx, &computepb.ListInstancesRequest{
|
|
|
|
|
Project: s.CredentialsConfig.ProjectId,
|
|
|
|
|
Zone: s.CredentialsConfig.Zone,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
_, err = it.Next()
|
|
|
|
|
if err != nil && err != iterator.Done {
|
|
|
|
|
return fmt.Errorf("error listing instances: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|