Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
67e711d
add cert provider for cert management
yanjiaxin534 Sep 18, 2025
ff5b3bf
add get certificate retry logic
yanjiaxin534 Sep 19, 2025
f5d79a4
fix json formate
yanjiaxin534 Sep 19, 2025
f463629
fix param
yanjiaxin534 Sep 19, 2025
cc10d56
fix \
yanjiaxin534 Sep 19, 2025
6cce19e
remove certmanager
yanjiaxin534 Sep 19, 2025
e4f778c
fix
yanjiaxin534 Sep 19, 2025
9c50fba
try to use cert manager
yanjiaxin534 Sep 19, 2025
2390cd0
cert provider fix
yanjiaxin534 Sep 20, 2025
a350525
add secret check in create cert
yanjiaxin534 Sep 21, 2025
e44cdf3
fix integration test1
yanjiaxin534 Sep 19, 2025
ace1d5d
fix test
yanjiaxin534 Sep 22, 2025
127b54e
fix ut
yanjiaxin534 Sep 22, 2025
dd5b7a1
refine createcert logic duration will from properties
yanjiaxin534 Sep 22, 2025
f186d06
fix type
yanjiaxin534 Sep 22, 2025
d7d0d92
add go mod
yanjiaxin534 Sep 22, 2025
bf49c08
fix test
yanjiaxin534 Sep 22, 2025
5714575
refine cert provider init
yanjiaxin534 Sep 22, 2025
8644d5f
remove cert provider nil check in solution vendor
yanjiaxin534 Sep 22, 2025
29d8d73
fix series number
yanjiaxin534 Sep 22, 2025
3f92994
delete no use
yanjiaxin534 Sep 22, 2025
c0aaea1
recover helper test
yanjiaxin534 Sep 22, 2025
1341c66
refactor cert provider location
yanjiaxin534 Sep 22, 2025
4bb39a5
fix symphony api json
yanjiaxin534 Sep 23, 2025
2d85e4c
get duration from provider config
yanjiaxin534 Sep 23, 2025
dd4abb7
Fix ut
yanjiaxin534 Sep 23, 2025
27b7948
add coa go mod
yanjiaxin534 Sep 23, 2025
3b263a9
fix test
yanjiaxin534 Sep 23, 2025
fa20723
add some retry time
yanjiaxin534 Sep 23, 2025
935df65
add retry
yanjiaxin534 Sep 23, 2025
c2e69ca
add false remove
yanjiaxin534 Sep 23, 2025
c2c987e
fix rust bug
yanjiaxin534 Sep 23, 2025
a6042ec
target & solution vendor & manager refacting
yanjiaxin534 Sep 24, 2025
61c62bb
no get cert provider
yanjiaxin534 Sep 24, 2025
b5e91c0
improve script
yanjiaxin534 Sep 24, 2025
2b95a6e
no need cert provider config
yanjiaxin534 Sep 24, 2025
f7621db
refine function expose
yanjiaxin534 Sep 24, 2025
86297ca
remove get cert provider
yanjiaxin534 Sep 24, 2025
b4efd62
refine dns commen name
yanjiaxin534 Sep 24, 2025
14ca85f
no need remote target name now
yanjiaxin534 Sep 24, 2025
f97352c
fix script
yanjiaxin534 Sep 24, 2025
71bbfc7
fix
yanjiaxin534 Sep 24, 2025
fc7e1e7
refine time const for cert
yanjiaxin534 Sep 24, 2025
2895e9d
use service name as common name
yanjiaxin534 Sep 25, 2025
fa66dd4
fix test
yanjiaxin534 Sep 25, 2025
0e6f580
improve error state
yanjiaxin534 Sep 25, 2025
3d0a757
add vendor context to solution manager in test
yanjiaxin534 Sep 25, 2025
f8fc0b6
fix test
yanjiaxin534 Sep 25, 2025
06f1319
add cert provider to ut
yanjiaxin534 Sep 25, 2025
fd518b9
fix test
yanjiaxin534 Sep 26, 2025
214f646
refine name
yanjiaxin534 Sep 28, 2025
3642797
fix k8s config
yanjiaxin534 Sep 28, 2025
830ad3a
fix test
yanjiaxin534 Sep 28, 2025
189109c
remove no use code
yanjiaxin534 Sep 29, 2025
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
2 changes: 0 additions & 2 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ require (
k8s.io/api v0.31.3
k8s.io/apimachinery v0.31.3
k8s.io/client-go v0.31.3

)

require (
Expand Down Expand Up @@ -172,7 +171,6 @@ require (
sigs.k8s.io/kustomize/api v0.17.2 // indirect
sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect
sigs.k8s.io/yaml v1.4.0

)

require (
Expand Down
164 changes: 164 additions & 0 deletions api/pkg/apis/v1alpha1/managers/solution/solution-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability"
observ_utils "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability/utils"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers"
certProvider "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/cert"
config "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/config"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/keylock"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/queue"
Expand All @@ -41,6 +42,8 @@ import (
var (
log = logger.NewLogger("coa.runtime")
apiOperationMetrics *metrics.Metrics
CAIssuer = os.Getenv("ISSUER_NAME")
ServiceName = os.Getenv("SYMPHONY_SERVICE_NAME")
)

var deploymentTypeMap = map[bool]string{
Expand All @@ -63,11 +66,16 @@ const (
DeploymentState = "DeployState"
DeploymentPlan = "DeploymentPlan"
OperationState = "OperationState"

// Certificate creation retry constants
CertCreationMaxRetries = 10
CertCreationRetryDelay = 2 * time.Second
)

type SolutionManager struct {
SummaryManager
TargetProviders map[string]tgt.ITargetProvider
CertProvider certProvider.ICertProvider
ConfigProvider config.IExtConfigProvider
SecretProvider secret.ISecretProvider
KeyLockProvider keylock.IKeyLockProvider
Expand All @@ -83,6 +91,7 @@ func (s *SolutionManager) Init(context *contexts.VendorContext, config managers.
if err != nil {
return err
}

s.TargetProviders = make(map[string]tgt.ITargetProvider)
for k, v := range providers {
if p, ok := v.(tgt.ITargetProvider); ok {
Expand Down Expand Up @@ -118,6 +127,14 @@ func (s *SolutionManager) Init(context *contexts.VendorContext, config managers.
return err
}

// Initialize cert provider using unified approach
certProvider, err := managers.GetCertProvider(config, providers)
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

why we have special case for cert provider initialization?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't need special case, fix it now.

s.CertProvider = certProvider
} else {
log.Warnf("Cert provider not configured: %v", err)
}

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

return nil
}

// CreateCertificateWithValidation creates a certificate with validation checks
// It validates that the certificate doesn't exist before creation and verifies creation success after
func (s *SolutionManager) CreateCertificateWithValidation(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 with retry mechanism: verify certificate was created successfully
log.InfofCtx(ctx, " M (Solution): validating certificate %s was created successfully with retry mechanism", certID)
for i := 0; i < CertCreationMaxRetries; i++ {
_, err = s.CertProvider.GetCert(ctx, certID, request.Namespace)
if err == nil {
log.InfofCtx(ctx, " M (Solution): working certificate %s created and validated successfully after %d attempts", certID, i+1)
return nil
}

if i < CertCreationMaxRetries-1 {
log.InfofCtx(ctx, " M (Solution): certificate %s not found on attempt %d/%d, waiting %v before retry. Error: %v",
certID, i+1, CertCreationMaxRetries, CertCreationRetryDelay, err)
time.Sleep(CertCreationRetryDelay)
} else {
log.ErrorfCtx(ctx, " M (Solution): certificate %s validation failed after %d attempts. Final error: %v",
certID, CertCreationMaxRetries, err)
}
}

return fmt.Errorf("certificate %s creation validation failed, certificate not found after creation with %d retries: %v",
certID, CertCreationMaxRetries, err)
}

// DeleteCertificateWithValidation deletes a certificate with validation checks
// It validates that the certificate exists before deletion and verifies deletion success after
func (s *SolutionManager) DeleteCertificateWithValidation(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 {
log.InfofCtx(ctx, " M (Solution): certificate %s does not exist, skipping deletion", certID)
return nil
}

// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

This check is strange. In this case, you should get the NotFound error and the first cert object should be nil.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this check is for delete failed

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
}

// isRemoteTargetDeployment checks if a deployment spec involves a remote target by looking for components of type "remote-agent"
func isRemoteTargetDeployment(deploymentSpec *model.DeploymentSpec) bool {
if deploymentSpec == nil {
return false
}

// check components in solution spec
if deploymentSpec.Solution.Spec == nil || len(deploymentSpec.Solution.Spec.Components) == 0 {
return false
}

// iterate over all components to find one with type "remote-agent"
for _, component := range deploymentSpec.Solution.Spec.Components {
if component.Type == "remote-agent" {
return true
}
}

return false
}

// handleWorkingCertManagement manages working certificates for remote targets
func (s *SolutionManager) handleWorkingCertManagement(ctx context.Context, deployment model.DeploymentSpec, remove bool, namespace string) error {
log.InfofCtx(ctx, "V (Solution): handleWorkingCertManagement for remote target: %s, remove: %t", deployment.Solution.ObjectMeta.Name, remove)

if remove {
// Delete working certificate when removing remote target
err := s.DeleteCertificateWithValidation(ctx, deployment.Solution.ObjectMeta.Name, namespace)
if err != nil {
return fmt.Errorf("failed to delete working certificate for remote target %s: %w", deployment.Solution.ObjectMeta.Name, err)
}
log.InfofCtx(ctx, "V (Solution): successfully deleted working certificate for remote target: %s", deployment.Solution.ObjectMeta.Name)
} else {
// Create working certificate for remote target
err := s.CreateCertificateWithValidation(ctx, deployment.Solution.ObjectMeta.Name, s.createCertRequest(deployment.Solution.ObjectMeta.Name, namespace))
if err != nil {
return fmt.Errorf("failed to create or update working certificate for remote target %s: %w", deployment.Solution.ObjectMeta.Name, err)
} else {
log.InfofCtx(ctx, "V (Solution): successfully created working certificate for remote target: %s", deployment.Solution.ObjectMeta.Name)
}
}

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 @@ -197,6 +338,7 @@ func (s *SolutionManager) AsyncReconcile(ctx context.Context, deployment model.D
s.KeyLockProvider.UnLock(lockName)
return summary, err
}

// get the components count for the deployment
componentCount := len(deployment.Solution.Spec.Components)
apiOperationMetrics.ApiComponentCount(
Expand Down Expand Up @@ -251,6 +393,14 @@ func (s *SolutionManager) AsyncReconcile(ctx context.Context, deployment model.D
stepList = append(stepList, step)
}
initalPlan.Steps = stepList
// Handle working certificate management for remote targets
if isRemoteTargetDeployment(&deployment) {
err = s.handleWorkingCertManagement(ctx, deployment, remove, namespace)
if err != nil {
log.ErrorfCtx(ctx, "V (Solution): failed to handle working cert management: %s", err.Error())
return summary, err
}
}
log.InfoCtx(ctx, "publish topic for object %s", deployment.Instance.ObjectMeta.Name)
s.VendorContext.Publish(model.DeploymentPlanTopic, v1alpha2.Event{
Metadata: map[string]string{
Expand Down Expand Up @@ -1895,3 +2045,17 @@ func (s *SolutionManager) getOperationState(ctx context.Context, operationId str
}
return ret, err
}

// createCertRequest creates a certificate request with required fields, letting the cert provider use its configured defaults for Duration and RenewBefore
func (s *SolutionManager) createCertRequest(targetName string, namespace string) certProvider.CertRequest {
// Create request with required fields - provider will use its configured defaults for Duration and RenewBefore only
return certProvider.CertRequest{
TargetName: targetName,
Copy link
Contributor

@iwangjintian iwangjintian Sep 29, 2025

Choose a reason for hiding this comment

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

It should be cert name rather than targetname.
The cert request does not only work for remote agent cert scenarios

Namespace: namespace,
CommonName: ServiceName,
IssuerName: CAIssuer,
Subject: map[string]interface{}{
"organizations": []interface{}{ServiceName},
},
}
}
Loading
Loading