Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions api/pkg/apis/v1alpha1/managers/solution/solution-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/managers/solution/metrics"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model"
sp "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/providers"
certProvider "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/providers/cert"
tgt "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/providers/target"
api_utils "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/utils"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2"
Expand Down Expand Up @@ -68,6 +69,7 @@ const (
type SolutionManager struct {
SummaryManager
TargetProviders map[string]tgt.ITargetProvider
CertProvider certProvider.ICertProvider
ConfigProvider config.IExtConfigProvider
SecretProvider secret.ISecretProvider
KeyLockProvider keylock.IKeyLockProvider
Expand Down Expand Up @@ -118,6 +120,15 @@ func (s *SolutionManager) Init(context *contexts.VendorContext, config managers.
return err
}

// Initialize cert provider
if certProviderInstance, exists := providers["working-cert"]; exists {
if cp, ok := certProviderInstance.(certProvider.ICertProvider); ok {
s.CertProvider = cp
} else {
return fmt.Errorf("working-cert provider does not implement ICertProvider interface")
}
}

if v, ok := config.Properties["isTarget"]; ok {
b, err := strconv.ParseBool(v)
if err == nil || b {
Expand Down Expand Up @@ -159,6 +170,77 @@ func (s *SolutionManager) Init(context *contexts.VendorContext, config managers.

return nil
}

// GetCertProvider returns the cert provider instance for certificate management operations
func (s *SolutionManager) GetCertProvider() certProvider.ICertProvider {
return s.CertProvider
}

// SafeCreateWorkingCert creates a working certificate with validation checks
// It validates that the certificate doesn't exist before creation and verifies creation success after
func (s *SolutionManager) SafeCreateWorkingCert(ctx context.Context, certID string, request certProvider.CertRequest) error {
if s.CertProvider == nil {
return fmt.Errorf("cert provider not initialized")
}

// Pre-creation validation: check if certificate already exists
log.InfofCtx(ctx, " M (Solution): validating certificate %s doesn't exist before creation", certID)
_, err := s.CertProvider.GetCert(ctx, certID, request.Namespace)
if err == nil {
log.InfofCtx(ctx, " M (Solution): certificate %s already exists, skipping creation", certID)
return nil
}

// Create the certificate
log.InfofCtx(ctx, " M (Solution): creating working certificate %s", certID)
err = s.CertProvider.CreateCert(ctx, request)
if err != nil {
return fmt.Errorf("failed to create certificate %s: %v", certID, err)
}

// Post-creation validation: verify certificate was created successfully
log.InfofCtx(ctx, " M (Solution): validating certificate %s was created successfully", certID)
_, err = s.CertProvider.GetCert(ctx, certID, request.Namespace)
if err != nil {
return fmt.Errorf("certificate %s creation validation failed, certificate not found after creation: %v", certID, err)
}

log.InfofCtx(ctx, " M (Solution): working certificate %s created and validated successfully", certID)
return nil
}

// SafeDeleteWorkingCert deletes a working certificate with validation checks
// It validates that the certificate exists before deletion and verifies deletion success after
func (s *SolutionManager) SafeDeleteWorkingCert(ctx context.Context, certID string, namespace string) error {
if s.CertProvider == nil {
return fmt.Errorf("cert provider not initialized")
}

// Pre-deletion validation: check if certificate exists
log.InfofCtx(ctx, " M (Solution): validating certificate %s exists before deletion", certID)
_, err := s.CertProvider.GetCert(ctx, certID, namespace)
if err != nil {
return fmt.Errorf("certificate %s not found, cannot delete: %v", certID, err)
}

// Delete the certificate
log.InfofCtx(ctx, " M (Solution): deleting working certificate %s", certID)
err = s.CertProvider.DeleteCert(ctx, certID, namespace)
if err != nil {
return fmt.Errorf("failed to delete certificate %s: %v", certID, err)
}

// Post-deletion validation: verify certificate was deleted successfully
log.InfofCtx(ctx, " M (Solution): validating certificate %s was deleted successfully", certID)
_, err = s.CertProvider.GetCert(ctx, certID, namespace)
if err == nil {
return fmt.Errorf("certificate %s deletion validation failed, certificate still exists after deletion", certID)
}

log.InfofCtx(ctx, " M (Solution): working certificate %s deleted and validated successfully", certID)
return nil
}

func (s *SolutionManager) AsyncReconcile(ctx context.Context, deployment model.DeploymentSpec, remove bool, namespace string, targetName string) (model.SummarySpec, error) {
lockName := api_utils.GenerateKeyLockName(namespace, deployment.Instance.ObjectMeta.Name)
s.KeyLockProvider.Lock(lockName)
Expand Down Expand Up @@ -1895,3 +1977,17 @@ func (s *SolutionManager) getOperationState(ctx context.Context, operationId str
}
return ret, err
}

// CreateCertRequest creates a certificate request for the given target and namespace
func (s *SolutionManager) CreateCertRequest(targetName string, namespace string) certProvider.CertRequest {
return certProvider.CertRequest{
TargetName: targetName,
Namespace: namespace,
Duration: time.Hour * 2160, // 90 days default
RenewBefore: time.Hour * 360, // 15 days before expiration
CommonName: fmt.Sprintf("symphony-%s", targetName),
DNSNames: []string{targetName, fmt.Sprintf("%s.%s", targetName, namespace)},
IssuerName: "symphony-ca",
ServiceName: "symphony-service",
}
}
10 changes: 10 additions & 0 deletions api/pkg/apis/v1alpha1/managers/targets/targets-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/eclipse-symphony/symphony/api/constants"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model"
certProvider "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/providers/cert"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/validation"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/contexts"
Expand All @@ -37,6 +38,7 @@ type TargetsManager struct {
needValidate bool
TargetValidator validation.TargetValidator
SecretProvider secret.ISecretProvider
CertProvider certProvider.ICertProvider
}

func (s *TargetsManager) Init(context *contexts.VendorContext, config managers.ManagerConfig, providers map[string]providers.IProvider) error {
Expand All @@ -61,6 +63,9 @@ func (s *TargetsManager) Init(context *contexts.VendorContext, config managers.M
if c, ok := p.(secret.ISecretProvider); ok {
s.SecretProvider = c
}
if c, ok := p.(certProvider.ICertProvider); ok {
s.CertProvider = c
}
}

return nil
Expand Down Expand Up @@ -308,3 +313,8 @@ func (t *TargetsManager) targetInstanceLookup(ctx context.Context, name string,
}
return len(instanceList) > 0, nil
}

// GetCertProvider returns the certificate provider for read-only access to certificates
func (t *TargetsManager) GetCertProvider() certProvider.ICertProvider {
return t.CertProvider
}
1 change: 1 addition & 0 deletions api/pkg/apis/v1alpha1/model/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type DeploymentSpec struct {
Hash string `json:"hash,omitempty"`
IsDryRun bool `json:"isDryRun,omitempty"`
IsInActive bool `json:"isInActive,omitempty"`
RemoteTargetName string `json:"remoteTargetName,omitempty"`
}

func (d DeploymentSpec) GetComponentSlice() []ComponentSpec {
Expand Down
59 changes: 59 additions & 0 deletions api/pkg/apis/v1alpha1/providers/cert/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
* SPDX-License-Identifier: MIT
*/

package cert

import (
"context"
"time"
)

// ICertProvider defines the interface for certificate management
type ICertProvider interface {
// CreateCert creates a certificate for the specified target
CreateCert(ctx context.Context, req CertRequest) error

// DeleteCert deletes the certificate for the specified target
DeleteCert(ctx context.Context, targetName, namespace string) error

// GetCert retrieves the certificate for the specified target (read-only)
GetCert(ctx context.Context, targetName, namespace string) (*CertResponse, error)

// RotateCert rotates/renews the certificate for the specified target
RotateCert(ctx context.Context, targetName, namespace string) error

// CheckCertStatus checks if the certificate is ready and valid
CheckCertStatus(ctx context.Context, targetName, namespace string) (*CertStatus, error)
}

// CertRequest represents a certificate creation request
type CertRequest struct {
TargetName string `json:"targetName"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we name this field more generic? as a cert request, what does target name mean?

Namespace string `json:"namespace"`
Duration time.Duration `json:"duration"`
RenewBefore time.Duration `json:"renewBefore"`
CommonName string `json:"commonName"`
DNSNames []string `json:"dnsNames"`
IssuerName string `json:"issuerName"`
ServiceName string `json:"serviceName"`
}

// CertResponse represents the certificate data
type CertResponse struct {
PublicKey string `json:"publicKey"`
PrivateKey string `json:"privateKey"`
ExpiresAt time.Time `json:"expiresAt"`
SerialNumber string `json:"serialNumber"`
}

// CertStatus represents the certificate status
type CertStatus struct {
Ready bool `json:"ready"`
Reason string `json:"reason"`
Message string `json:"message"`
LastUpdate time.Time `json:"lastUpdate"`
NextRenewal time.Time `json:"nextRenewal"`
}
Loading
Loading