Skip to content

Commit

Permalink
bootstrap
Browse files Browse the repository at this point in the history
  • Loading branch information
iwangjintian committed Dec 25, 2024
1 parent 9b86bbd commit 522bf45
Show file tree
Hide file tree
Showing 30 changed files with 860 additions and 41 deletions.
8 changes: 8 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ RUN mkdir /workspace
COPY ./packages /workspace/packages
COPY ./coa /workspace/coa
COPY ./api /workspace/api
COPY ./remote-agent /workspace/remote-agent

WORKDIR /workspace/remote-agent
RUN CGO_ENABLED=${CGO_ENABLED} GOOS=linux GOARCH=amd64 go build -o /remote-agent/remote-agent
RUN CGO_ENABLED=${CGO_ENABLED} GOOS=windows GOARCH=amd64 go build -o /remote-agent/remote-agent.exe

WORKDIR /workspace/api
# File permissions are not preserved when copying files in ADO.
RUN chmod +x pkg/apis/v1alpha1/providers/target/script/mock-*.sh
Expand Down Expand Up @@ -47,6 +53,8 @@ RUN \
ADD https://github.com/golang/go/raw/master/lib/time/zoneinfo.zip /zoneinfo.zip
ENV ZONEINFO=/zoneinfo.zip
COPY --from=build /dist /
RUN mkdir /remote-agent
COPY --from=build /remote-agent /remote-agent
ADD ./api/symphony-api.json /
EXPOSE 8080
EXPOSE 8081
Expand Down
12 changes: 12 additions & 0 deletions api/pkg/apis/v1alpha1/managers/targets/targets-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/registry"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/secret"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/states"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/utils"

Expand All @@ -32,6 +33,7 @@ type TargetsManager struct {
RegistryProvider registry.IRegistryProvider
needValidate bool
TargetValidator validation.TargetValidator
SecretProvider secret.ISecretProvider
}

func (s *TargetsManager) Init(context *contexts.VendorContext, config managers.ManagerConfig, providers map[string]providers.IProvider) error {
Expand All @@ -51,6 +53,16 @@ func (s *TargetsManager) Init(context *contexts.VendorContext, config managers.M
// s.TargetValidator = validation.NewTargetValidator(s.targetInstanceLookup, s.targetUniqueNameLookup)
s.TargetValidator = validation.NewTargetValidator(nil, s.targetUniqueNameLookup)
}

for _, p := range providers {
if c, ok := p.(secret.ISecretProvider); ok {
s.SecretProvider = c
}
}

if s.SecretProvider == nil {
return v1alpha2.NewCOAError(nil, "Secret provider is not found", v1alpha2.BadConfig)
}
return nil
}

Expand Down
2 changes: 2 additions & 0 deletions api/pkg/apis/v1alpha1/providers/secret/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ func (s *K8sSecretProvider) Read(ctx context.Context, name string, field string,
defer observ_utils.CloseSpanWithError(span, &err)
defer observ_utils.EmitUserDiagnosticsLogs(ctx, &err)

jsonData, err := json.Marshal(localContext)
sLog.DebugfCtx(ctx, " P (K8s Secret): read secret %s field %s with context %s", name, field, string(jsonData))
namespace := utils.GetNamespaceFromContext(localContext)
secret, err := s.Clientset.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
Expand Down
219 changes: 189 additions & 30 deletions api/pkg/apis/v1alpha1/vendors/targets-vendor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
package vendors

import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"time"

Expand All @@ -21,13 +25,22 @@ import (
observ_utils "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability/utils"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/pubsub"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/states"
coa_utils "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/utils"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/vendors"
"github.com/eclipse-symphony/symphony/coa/pkg/logger"
"github.com/golang-jwt/jwt/v4"
"github.com/valyala/fasthttp"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var tLog = logger.NewLogger("coa.runtime")
var (
tLog = logger.NewLogger("coa.runtime")
CAIssuer = os.Getenv("ISSUER_NAME")
ServiceName = os.Getenv("SYMPHONY_SERVICE_NAME")
AgentPath = os.Getenv("AGENT_PATH")
)

type TargetsVendor struct {
vendors.Vendor
Expand Down Expand Up @@ -55,6 +68,7 @@ func (e *TargetsVendor) Init(config vendors.VendorConfig, factories []managers.I
if e.TargetsManager == nil {
return v1alpha2.NewCOAError(nil, "targets manager is not supplied", v1alpha2.MissingConfig)
}

return nil
}

Expand All @@ -72,10 +86,11 @@ func (o *TargetsVendor) GetEndpoints() []v1alpha2.Endpoint {
Parameters: []string{"name?"},
},
{
Methods: []string{fasthttp.MethodPost},
Route: route + "/bootstrap",
Version: o.Version,
Handler: o.onBootstrap,
Methods: []string{fasthttp.MethodPost},
Route: route + "/bootstrap",
Version: o.Version,
Handler: o.onBootstrap,
Parameters: []string{"name?"},
},
{
Methods: []string{fasthttp.MethodPost},
Expand Down Expand Up @@ -288,43 +303,187 @@ func (c *TargetsVendor) onBootstrap(request v1alpha2.COARequest) v1alpha2.COARes
})
defer span.End()
tLog.InfofCtx(ctx, "V (Targets) : onBootstrap, method: %s", request.Method)
id := request.Parameters["__name"]
namespace, exist := request.Parameters["namespace"]
if !exist {
namespace = constants.DefaultScope
}
osPlatform, exist := request.Parameters["osPlatform"]
if !exist {
osPlatform = "linux"
}
switch request.Method {
case fasthttp.MethodPost:
var authRequest AuthRequest
err := json.Unmarshal(request.Body, &authRequest)
if err != nil || authRequest.UserName != "symphony-test" {
subject := fmt.Sprintf("CN=%s-%s.%s", namespace, id, ServiceName)
_, err := c.TargetsManager.GetState(ctx, id, namespace)
if err != nil {
tLog.InfofCtx(ctx, "V (Targets) : onBootstrap target %s in namespace %s not found", id, namespace)
binding := request.Parameters["with-binding"]
var target model.TargetState
err := json.Unmarshal(request.Body, &target)
if err != nil {
tLog.ErrorfCtx(ctx, "V (Targets) : onBootstrap failed - %s", err.Error())
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.InternalError,
Body: []byte(err.Error()),
})
}
if target.ObjectMeta.Name == "" {
target.ObjectMeta.Name = id
}
if binding != "" {
if binding == "staging" {
target.Spec.ForceRedeploy = true
if target.Spec.Topologies == nil {
target.Spec.Topologies = make([]model.TopologySpec, 0)
}
found := false
for _, t := range target.Spec.Topologies {
if t.Bindings != nil {
for _, b := range t.Bindings {
if b.Role == "instance" && b.Provider == "providers.target.staging" {
found = true
break
}
}
}
}
if !found {
newb := model.BindingSpec{
Role: "instance",
Provider: "providers.target.staging",
Config: map[string]string{
"inCluster": "true",
"targetName": id,
},
}
if len(target.Spec.Topologies) == 0 {
target.Spec.Topologies = append(target.Spec.Topologies, model.TopologySpec{})
}
if target.Spec.Topologies[len(target.Spec.Topologies)-1].Bindings == nil {
target.Spec.Topologies[len(target.Spec.Topologies)-1].Bindings = make([]model.BindingSpec, 0)
}
target.Spec.Topologies[len(target.Spec.Topologies)-1].Bindings = append(target.Spec.Topologies[len(target.Spec.Topologies)-1].Bindings, newb)
}
} else {
tLog.ErrorCtx(ctx, "V (Targets) : onBootstrap failed - invalid binding")
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.BadRequest,
Body: []byte("invalid binding, supported is: 'staging'"),
})
}
}
err = c.TargetsManager.UpsertState(ctx, id, target)
if err != nil {
tLog.ErrorfCtx(ctx, "V (Targets) : onRegistry failed - %s", err.Error())
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.InternalError,
Body: []byte(err.Error()),
})
}
}
// create working cert
gvk := schema.GroupVersionKind{
Group: "cert-manager.io",
Version: "v1",
Kind: "Certificate",
}

// Create an unstructured object
cert := &unstructured.Unstructured{}
cert.SetGroupVersionKind(gvk)

// Set the metadata
cert.SetName(id)
cert.SetNamespace(namespace)

secretName := fmt.Sprintf("%s-tls", id)
// Set the spec fields
spec := map[string]interface{}{
"secretName": secretName,
"duration": "2160h", // 90 days
"renewBefore": "360h", // 15 days
"commonName": subject,
"dnsNames": []string{
subject,
},
"issuerRef": map[string]interface{}{
"name": CAIssuer,
"kind": "Issuer",
},
"subject": map[string]interface{}{
"organizations": []interface{}{
ServiceName,
},
},
}

// Set the spec in the unstructured object
cert.Object["spec"] = spec

upsertRequest := states.UpsertRequest{
Value: states.StateEntry{
ID: id,
Body: cert.Object,
},
Metadata: map[string]interface{}{
"namespace": namespace,
"group": gvk.Group,
"version": gvk.Version,
"resource": "certificates",
"kind": gvk.Kind,
},
}
jsonData, _ := json.Marshal(upsertRequest)
tLog.InfofCtx(ctx, "V (Targets) : create certificate object - %s", jsonData)
_, err = c.TargetsManager.StateProvider.Upsert(ctx, upsertRequest)
if err != nil {
tLog.ErrorfCtx(ctx, "V (Targets) : onBootstrap failed - %s", err.Error())
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.Unauthorized,
State: v1alpha2.InternalError,
Body: []byte(err.Error()),
})
}
mySigningKey := []byte("SymphonyKey")
claims := MyCustomClaims{
authRequest.UserName,
jwt.RegisteredClaims{
// A usual scenario is to set the expiration time relative to the current time
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "symphony",
Subject: "symphony",
ID: "1",
Audience: []string{"*"},
},

// get secret
public, err := c.TargetsManager.SecretProvider.Read(ctx, secretName, "tls.crt", coa_utils.EvaluationContext{Namespace: namespace})
if err != nil {
tLog.ErrorfCtx(ctx, "V (Targets) : onBootstrap failed - %s", err.Error())
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.InternalError,
Body: []byte(err.Error()),
})
}
private, err := c.TargetsManager.SecretProvider.Read(ctx, secretName, "tls.key", coa_utils.EvaluationContext{Namespace: namespace})
if err != nil {
tLog.ErrorfCtx(ctx, "V (Targets) : onBootstrap failed - %s", err.Error())
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.InternalError,
Body: []byte(err.Error()),
})
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, _ := token.SignedString(mySigningKey)
filePath := fmt.Sprintf("%s/%s", AgentPath, "remote-agent")
if osPlatform == "windows" {
filePath = fmt.Sprintf("%s/%s", AgentPath, "remote-agent.exe")
}

resp := v1alpha2.COAResponse{
State: v1alpha2.OK,
Body: []byte(`{"accessToken":"` + ss + `", "tokenType": "Bearer"}`),
ContentType: "application/json",
fileContent, err := ioutil.ReadFile(filePath)
if err != nil {
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.InternalError,
Body: []byte(fmt.Sprintf("Error reading file: %v", err)),
ContentType: "text/plain",
})
}

observ_utils.UpdateSpanStatusFromCOAResponse(span, resp)
return resp
// Base64 encode the file content
encodedFileContent := base64.StdEncoding.EncodeToString(fileContent)
return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{
State: v1alpha2.OK,
Body: []byte(fmt.Sprintf("{\"public\":\"%s\",\"private\":\"%s\", \"file\":\"%s\"}", public, private, encodedFileContent)),
})

}
tLog.ErrorCtx(ctx, "V (Targets) : onRegistry failed - method not allowed")
resp := v1alpha2.COAResponse{
Expand Down
Loading

0 comments on commit 522bf45

Please sign in to comment.