diff --git a/apim-apk-agent/internal/utils/apis_fetcher.go b/apim-apk-agent/internal/utils/apis_fetcher.go index ec1e4a3e..086d4fb3 100644 --- a/apim-apk-agent/internal/utils/apis_fetcher.go +++ b/apim-apk-agent/internal/utils/apis_fetcher.go @@ -78,7 +78,6 @@ func FetchAPIsOnEvent(conf *config.Config, apiUUID *string, k8sClient client.Cli logger.LoggerSync.Infof("API file found: " + file.Name) // Todo: Read the apis.zip and extract the api.zip,deployments.json } - if err != nil { logger.LoggerSync.Errorf("Error while reading zip: %v", err) return nil, err @@ -108,13 +107,18 @@ func FetchAPIsOnEvent(conf *config.Config, apiUUID *string, k8sClient client.Cli logger.LoggerSync.Errorf("Error while decoding the API Project Artifact: %v", decodingError) return nil, err } - apkConf, apiUUID, revisionID, apkErr := transformer.GenerateAPKConf(artifact.APIJson, artifact.ClientCerts) + apkConf, apiUUID, revisionID, apkErr := transformer.GenerateAPKConf(artifact.APIJson, artifact.CertArtifact) if apkErr != nil { logger.LoggerSync.Errorf("Error while generating APK-Conf: %v", apkErr) return nil, err } + logger.LoggerSync.Debug("APK Conf:", apkConf) + certContainer := transformer.CertContainer{ + ClientCertObj: artifact.CertMeta, + EndpointCertObj: artifact.EndpointCertMeta, + } k8ResourceEndpoint := conf.DataPlane.K8ResourceEndpoint - crResponse, err := transformer.GenerateCRs(apkConf, artifact.Swagger, k8ResourceEndpoint) + crResponse, err := transformer.GenerateCRs(apkConf, artifact.Swagger, certContainer, k8ResourceEndpoint) if err != nil { logger.LoggerSync.Errorf("Error occured in receiving the updated CRDs: %v", err) return nil, err diff --git a/apim-apk-agent/pkg/transformer/api_model.go b/apim-apk-agent/pkg/transformer/api_model.go index 9a0235d2..3f55e455 100644 --- a/apim-apk-agent/pkg/transformer/api_model.go +++ b/apim-apk-agent/pkg/transformer/api_model.go @@ -108,11 +108,38 @@ type APIYaml struct { // APIArtifact represents the artifact details of an API, including api details, environment configuration, // Swagger definition, deployment descriptor, and revision ID extracted from the API Project Zip. type APIArtifact struct { - APIJson string `json:"apiJson"` - APIFileName string `json:"apiFileName"` - EnvConfig string `json:"envConfig"` - Swagger string `json:"swagger"` - DeploymentDescriptor string `json:"deploymentDescriptor"` - ClientCerts string `json:"clientCert"` - RevisionID uint32 `json:"revisionId"` + APIJson string `json:"apiJson"` + APIFileName string `json:"apiFileName"` + EnvConfig string `json:"envConfig"` + Swagger string `json:"swagger"` + DeploymentDescriptor string `json:"deploymentDescriptor"` + CertArtifact CertificateArtifact `json:"certArtifact"` + RevisionID uint32 `json:"revisionId"` + CertMeta CertMetadata `json:"certMeta"` + EndpointCertMeta EndpointCertMetadata `json:"endpintCertMeta"` +} + +// CertificateArtifact stores the parsed file content created inside the API project zip upon enabling certificate aided security options +type CertificateArtifact struct { + ClientCerts string `json:"clientCert"` + EndpointCerts string `json:"endpointCert"` +} + +// CertMetadata marks the availability of the cert files provided by the client and their contents +type CertMetadata struct { + CertAvailable bool `json:"certAvailable"` + ClientCertFiles map[string]string `json:"clientCertFiles"` +} + +// EndpointCertMetadata marks the availability of the endpoint certificates and stores the cert contents +type EndpointCertMetadata struct { + CertAvailable bool `json:"certAvailable"` + EndpointCertFiles map[string]string `json:"endpointCertFiles"` +} + +// CertContainer acts as a wrapper to hold onto all the certificate details for both endpoint and client-side security configs +// belong to a particular API Project +type CertContainer struct { + ClientCertObj CertMetadata + EndpointCertObj EndpointCertMetadata } diff --git a/apim-apk-agent/pkg/transformer/apk_model.go b/apim-apk-agent/pkg/transformer/apk_model.go index d304d846..b8474967 100644 --- a/apim-apk-agent/pkg/transformer/apk_model.go +++ b/apim-apk-agent/pkg/transformer/apk_model.go @@ -17,6 +17,18 @@ package transformer +// EndpointCertificate struct stores the the alias and the name for a particular endpoint security configuration +type EndpointCertificate struct { + Name string `yaml:"secretName"` + Key string `yaml:"secretKey"` +} + +// EndpointConfiguration stores the data related to endpoints and their related +type EndpointConfiguration struct { + Endpoint string `yaml:"endpoint,omitempty"` + EndCertificate EndpointCertificate `yaml:"certificate,omitempty"` +} + // AdditionalProperty stores the custom properties set by the user for a particular API type AdditionalProperty struct { Name string `yaml:"name"` @@ -43,14 +55,14 @@ type AuthConfiguration struct { } // Endpoint represents an API endpoint. -type Endpoint struct { - Endpoint string `yaml:"endpoint,omitempty"` -} +// type Endpoint struct { +// Endpoint string `yaml:"endpoint,omitempty"` +// } -// EndpointConfiguration holds production and sandbox endpoints. -type EndpointConfiguration struct { - Production *Endpoint `yaml:"production,omitempty"` - Sandbox *Endpoint `yaml:"sandbox,omitempty"` +// EndpointConfigurations holds production and sandbox endpoints. +type EndpointConfigurations struct { + Production *EndpointConfiguration `yaml:"production,omitempty"` + Sandbox *EndpointConfiguration `yaml:"sandbox,omitempty"` } // OperationPolicies organizes request and response policies for an API operation. @@ -81,17 +93,17 @@ type VHost struct { // API represents an main API type definition type API struct { - Name string `yaml:"name,omitempty"` - ID string `yaml:"id,omitempty"` - Version string `yaml:"version,omitempty"` - Context string `yaml:"basePath,omitempty"` - Type string `yaml:"type,omitempty"` - DefaultVersion bool `yaml:"defaultVersion"` - DefinitionPath string `yaml:"definitionPath,omitempty"` - EndpointConfigurations *EndpointConfiguration `yaml:"endpointConfigurations,omitempty"` - Operations *[]Operation `yaml:"operations,omitempty"` - Authentication *[]AuthConfiguration `yaml:"authentication,omitempty"` - CorsConfig *CORSConfiguration `yaml:"corsConfiguration,omitempty"` - AdditionalProperties *[]AdditionalProperty `yaml:"additionalProperties,omitempty"` - SubscriptionValidation bool `yaml:"subscriptionValidation,omitempty"` + Name string `yaml:"name,omitempty"` + ID string `yaml:"id,omitempty"` + Version string `yaml:"version,omitempty"` + Context string `yaml:"basePath,omitempty"` + Type string `yaml:"type,omitempty"` + DefaultVersion bool `yaml:"defaultVersion"` + DefinitionPath string `yaml:"definitionPath,omitempty"` + EndpointConfigurations *EndpointConfigurations `yaml:"endpointConfigurations,omitempty"` + Operations *[]Operation `yaml:"operations,omitempty"` + Authentication *[]AuthConfiguration `yaml:"authentication,omitempty"` + CorsConfig *CORSConfiguration `yaml:"corsConfiguration,omitempty"` + AdditionalProperties *[]AdditionalProperty `yaml:"additionalProperties,omitempty"` + SubscriptionValidation bool `yaml:"subscriptionValidation,omitempty"` } diff --git a/apim-apk-agent/pkg/transformer/endpoint_cert.go b/apim-apk-agent/pkg/transformer/endpoint_cert.go new file mode 100644 index 00000000..de8e00a7 --- /dev/null +++ b/apim-apk-agent/pkg/transformer/endpoint_cert.go @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package transformer + +// EndpointCert holds the data belongs to a single endpoint certificate configuration +type EndpointCert struct { + Alias string `json:"alias"` + Endpoint string `json:"endpoint"` + Certificate string `json:"certificate"` + TenantID int `json:"tenantId"` +} + +// EndpointCertDescriptor contains data related to one or more endpoint certificates for an API +type EndpointCertDescriptor struct { + EndpointCertData []EndpointCert `json:"data"` +} diff --git a/apim-apk-agent/pkg/transformer/transformer.go b/apim-apk-agent/pkg/transformer/transformer.go index 98a0f0d5..c662a808 100644 --- a/apim-apk-agent/pkg/transformer/transformer.go +++ b/apim-apk-agent/pkg/transformer/transformer.go @@ -32,6 +32,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "io" "mime/multipart" @@ -49,7 +50,7 @@ import ( ) // GenerateAPKConf will Generate the mapped .apk-conf file for a given API Project zip -func GenerateAPKConf(APIJson string, clientCerts string) (string, string, uint32, error) { +func GenerateAPKConf(APIJson string, certArtifact CertificateArtifact) (string, string, uint32, error) { apk := &API{} @@ -94,19 +95,31 @@ func GenerateAPKConf(APIJson string, clientCerts string) (string, string, uint32 apk.Operations = &apkOperations - apk.EndpointConfigurations = &EndpointConfiguration{ - // For private PPDPs, we need to treat the token type to be SANDBOX as it is tested by developers. - Sandbox: &Endpoint{ - Endpoint: apiYamlData.EndpointConfig.SandboxEndpoints.URL}, - Production: &Endpoint{ - Endpoint: apiYamlData.EndpointConfig.ProductionEndpoints.URL}, + //Adding Endpoint-certificate configurations to the conf + var endpointCertList EndpointCertDescriptor + endCertAvailable := false + + if certArtifact.EndpointCerts != "" { + certErr := json.Unmarshal([]byte(certArtifact.EndpointCerts), &endpointCertList) + if certErr != nil { + logger.LoggerTransformer.Errorf("Error while unmarshalling endpoint_cert.json content: ", apiYamlError) + return "", "null", 0, certErr + } + endCertAvailable = true } + sandboxURL := apiYamlData.EndpointConfig.SandboxEndpoints.URL + prodURL := apiYamlData.EndpointConfig.ProductionEndpoints.URL + endpointRes := getEndpointConfigs(sandboxURL, prodURL, endCertAvailable, endpointCertList) + + apk.EndpointConfigurations = &endpointRes + + //Adding client-certificate configurations to the conf var certList CertDescriptor certAvailable := false - if clientCerts != "" { - certErr := json.Unmarshal([]byte(clientCerts), &certList) + if certArtifact.ClientCerts != "" { + certErr := json.Unmarshal([]byte(certArtifact.ClientCerts), &certList) if certErr != nil { logger.LoggerTransformer.Errorf("Error while unmarshalling client_cert.json content: ", apiYamlError) return "", "null", 0, certErr @@ -235,9 +248,40 @@ func mapAuthConfigs(authHeader string, secSchemes []string, certAvailable bool, return authConfigs } +// getEndpointConfigs will map the endpoints and there security configurations and returns them +// TODO: Currently the APK-Conf does not support giving multiple certs for a particular endpoint. +// After fixing this, the following logic should be changed to map multiple cert configs +func getEndpointConfigs(sandboxURL string, prodURL string, endCertAvailable bool, endpointCertList EndpointCertDescriptor) EndpointConfigurations { + var sandboxEndpointConf, prodEndpointConf EndpointConfiguration + sandboxEndpointConf.Endpoint = sandboxURL + prodEndpointConf.Endpoint = prodURL + if endCertAvailable { + for _, endCert := range endpointCertList.EndpointCertData { + if endCert.Endpoint == sandboxURL { + sandboxEndpointConf.EndCertificate = EndpointCertificate{ + Name: endCert.Alias, + Key: endCert.Certificate, + } + } + if endCert.Endpoint == prodURL { + prodEndpointConf.EndCertificate = EndpointCertificate{ + Name: endCert.Alias, + Key: endCert.Certificate, + } + } + } + } + + epconfigs := EndpointConfigurations{ + Sandbox: &sandboxEndpointConf, + Production: &prodEndpointConf, + } + return epconfigs +} + // GenerateCRs takes the .apk-conf, api definition, vHost and the organization for a particular API and then generate and returns // the relavant CRD set as a zip -func GenerateCRs(apkConf string, apiDefinition string, k8ResourceGenEndpoint string) (*K8sArtifacts, error) { +func GenerateCRs(apkConf string, apiDefinition string, certContainer CertContainer, k8ResourceGenEndpoint string) (*K8sArtifacts, error) { k8sArtifact := K8sArtifacts{HTTPRoutes: make(map[string]*gwapiv1b1.HTTPRoute), Backends: make(map[string]*dpv1alpha1.Backend), Scopes: make(map[string]*dpv1alpha1.Scope), Authentication: make(map[string]*dpv1alpha2.Authentication), APIPolicies: make(map[string]*dpv1alpha2.APIPolicy), InterceptorServices: make(map[string]*dpv1alpha1.InterceptorService), ConfigMaps: make(map[string]*corev1.ConfigMap), Secrets: make(map[string]*corev1.Secret), RateLimitPolicies: make(map[string]*dpv1alpha1.RateLimitPolicy)} if apkConf == "" { logger.LoggerTransformer.Error("Empty apk-conf parameter provided. Unable to generate CRDs.") @@ -433,6 +477,16 @@ func GenerateCRs(apkConf string, apiDefinition string, k8ResourceGenEndpoint str logger.LoggerSync.Errorf("[!]Unknown Kind parsed from the YAML File: %v", kind) } } + // Create ConfigMap to store the cert data if mTLS has enabled + if certContainer.ClientCertObj.CertAvailable { + createConfigMaps(certContainer.ClientCertObj.ClientCertFiles, &k8sArtifact) + } + + // Create ConfigMap to store the cert data if endpoint security has enabled + if certContainer.EndpointCertObj.CertAvailable { + createConfigMaps(certContainer.EndpointCertObj.EndpointCertFiles, &k8sArtifact) + } + return &k8sArtifact, nil } @@ -552,3 +606,31 @@ func generateSHA1Hash(input string) string { h.Write([]byte(input)) return hex.EncodeToString(h.Sum(nil)) } + +// createConfigMaps returns a marshalled yaml of ConfigMap kind after adding the given values +func createConfigMaps(certFiles map[string]string, k8sArtifact *K8sArtifacts) { + for confKey, confValue := range certFiles { + pathSegments := strings.Split(confKey, ".") + configName := pathSegments[0] + + //TODO: Have to take the version, namespace as parameters instead of hardcoding + cm := corev1.ConfigMap{} + cm.APIVersion = "v1" + cm.Kind = "ConfigMap" + cm.ObjectMeta.Name = configName + cm.ObjectMeta.Namespace = "apk-integration-test" + + if cm.ObjectMeta.Labels == nil { + cm.ObjectMeta.Labels = make(map[string]string) + } + + if cm.Data == nil { + cm.Data = make(map[string]string) + } + cm.Data[confKey] = confValue + certConfigMap := &cm + + logger.LoggerTransformer.Debugf("New ConfigMap Data: %v", *certConfigMap) + k8sArtifact.ConfigMaps[certConfigMap.ObjectMeta.Name] = certConfigMap + } +} diff --git a/apim-apk-agent/pkg/transformer/utils.go b/apim-apk-agent/pkg/transformer/utils.go index fb8d5a34..0a29b640 100644 --- a/apim-apk-agent/pkg/transformer/utils.go +++ b/apim-apk-agent/pkg/transformer/utils.go @@ -93,8 +93,43 @@ func readZipFile(file *zip.File) (*APIArtifact, error) { if err != nil { return nil, err } - apiArtifact.ClientCerts = string(certificateJSON) + apiArtifact.CertArtifact.ClientCerts = string(certificateJSON) + apiArtifact.CertMeta.CertAvailable = true } + + if strings.Contains(file.Name, "endpoint_certificates.json") { + endpointCertificateJSON, err := ReadContent(file) + if err != nil { + return nil, err + } + apiArtifact.CertArtifact.EndpointCerts = string(endpointCertificateJSON) + apiArtifact.EndpointCertMeta.CertAvailable = true + } + + if strings.Contains(file.Name, ".crt") { + certificateData, err := ReadContent(file) + if err != nil { + return nil, err + } + //NOTE:There is an issue in reading the certificate content. Even the same logic is been used, the + // .crt files in the Client-certificates gets parsed in base64 encoded version while the cert files in + // Endpoint-certtificate folder gets parsed as original value + pathSegments := strings.Split(file.Name, "/") + if strings.Contains(file.Name, "Client-certificates") { + if apiArtifact.CertMeta.ClientCertFiles == nil { + apiArtifact.CertMeta.ClientCertFiles = make(map[string]string) + } + apiArtifact.CertMeta.ClientCertFiles[pathSegments[len(pathSegments)-1]] = string(certificateData) + } + if strings.Contains(file.Name, "Endpoint-certificates") { + if apiArtifact.EndpointCertMeta.EndpointCertFiles == nil { + apiArtifact.EndpointCertMeta.EndpointCertFiles = make(map[string]string) + } + apiArtifact.EndpointCertMeta.EndpointCertFiles[pathSegments[len(pathSegments)-1]] = string(certificateData) + } + + } + } return apiArtifact, nil } @@ -127,7 +162,7 @@ func readAPIZipFile(file *zip.File, apiArtifact *APIArtifact) error { } if strings.Contains(file.Name, "client_certificates.json") { - apiArtifact.ClientCerts = string(content) + apiArtifact.CertArtifact.ClientCerts = string(content) } return nil