Skip to content

Commit

Permalink
Merge pull request #1068 from O-sura/main
Browse files Browse the repository at this point in the history
Extending API Project to APK Conf mapping
  • Loading branch information
CrowleyRajapakse authored Feb 8, 2024
2 parents 78397a8 + 37ba289 commit e83c1af
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 51 deletions.
4 changes: 2 additions & 2 deletions apim-apk-agent/internal/eventhub/dataloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func FetchAPIsOnStartUp(conf *config.Config, apiUUIDList []string, k8sClient cli

for _, artifact := range artifacts {
if artifact.APIJson != "" && artifact.DeploymentDescriptor != "" {
apkConf, _, apkErr := transformer.GenerateAPKConf(artifact.APIJson)
apkConf, apiUUID, revisionID, apkErr := transformer.GenerateAPKConf(artifact.APIJson, artifact.ClientCerts)

if apkErr != nil {
logger.LoggerSync.Errorf("Error while generating APK-Conf: %v", apkErr)
Expand All @@ -359,7 +359,7 @@ func FetchAPIsOnStartUp(conf *config.Config, apiUUIDList []string, k8sClient cli
return
}

crResponse, err := transformer.GenerateUpdatedCRs(apkConf, artifact.Swagger, k8ResourceEndpoint, deploymentDescriptor, artifact.APIFileName)
crResponse, err := transformer.GenerateUpdatedCRs(apkConf, artifact.Swagger, k8ResourceEndpoint, deploymentDescriptor, artifact.APIFileName, apiUUID, fmt.Sprint(revisionID))
if err != nil {
logger.LoggerSync.Errorf("Error occured in receiving the updated CRDs: %v", err)
return
Expand Down
4 changes: 2 additions & 2 deletions apim-apk-agent/internal/synchronizer/apis_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func FetchAPIsOnEvent(conf *config.Config, apiUUIDList []string, k8sClient clien
return
}

apkConf, _, apkErr := transformer.GenerateAPKConf(artifact.APIJson)
apkConf, apiUUID, revisionID, apkErr := transformer.GenerateAPKConf(artifact.APIJson, artifact.ClientCerts)

if apkErr != nil {
logger.LoggerSync.Errorf("Error while generating APK-Conf: %v", apkErr)
Expand All @@ -174,7 +174,7 @@ func FetchAPIsOnEvent(conf *config.Config, apiUUIDList []string, k8sClient clien
return
}

crResponse, err := transformer.GenerateUpdatedCRs(apkConf, artifact.Swagger, k8ResourceEndpoint, deploymentDescriptor, artifact.APIFileName)
crResponse, err := transformer.GenerateUpdatedCRs(apkConf, artifact.Swagger, k8ResourceEndpoint, deploymentDescriptor, artifact.APIFileName, apiUUID, fmt.Sprint(revisionID))
if err != nil {
logger.LoggerSync.Errorf("Error occured in receiving the updated CRDs: %v", err)
return
Expand Down
40 changes: 23 additions & 17 deletions apim-apk-agent/pkg/transformer/api_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ type CORSConfiguration struct {
AccessControlAllowMethods []string `yaml:"accessControlAllowMethods"`
}

// AdditionalPropertiesMap represents additional properties for an API in the form of a map.
type AdditionalPropertiesMap struct{}
// AdditionalProperties represents additional properties for an API in the form of a map.
type AdditionalProperties struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
DisplayInDevPortal bool `yaml:"display"`
}

// InterceptorService holds configuration details for configuring interceptor
// for a aperticular API requests or responses.
Expand Down Expand Up @@ -78,21 +82,22 @@ type APIMOperation struct {

// APIMApi represents an API along with it's all basic information and the operations.
type APIMApi struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
Version string `yaml:"version"`
Context string `yaml:"context"`
DefaultVersion bool `yaml:"isDefaultVersion"`
Type string `yaml:"type"`
AuthorizationHeader string `yaml:"authorizationHeader"`
SecuritySchemes []string `json:"securityScheme"`
AdditionalProperties []string `yaml:"additionalProperties"`
AdditionalPropertiesMap AdditionalPropertiesMap `yaml:"additionalPropertiesMap"`
CORSConfiguration CORSConfiguration `yaml:"corsConfiguration"`
EndpointConfig EndpointConfig `yaml:"endpointConfig"`
Operations []APIMOperation `yaml:"operations"`
OrganizationID string `yaml:"organizationId"`
RevisionID uint32 `yaml:"revisionId"`
ID string `yaml:"id"`
Name string `yaml:"name"`
Version string `yaml:"version"`
Context string `yaml:"context"`
DefaultVersion bool `json:"isDefaultVersion"`
Type string `yaml:"type"`
AuthorizationHeader string `yaml:"authorizationHeader"`
SecuritySchemes []string `json:"securityScheme"`
AdditionalProperties []AdditionalProperties `yaml:"additionalProperties"`
// AdditionalPropertiesMap []AdditionalPropertiesMap `yaml:"additionalPropertiesMap"`
CORSConfiguration CORSConfiguration `yaml:"corsConfiguration"`
EndpointConfig EndpointConfig `yaml:"endpointConfig"`
Operations []APIMOperation `yaml:"operations"`
OrganizationID string `yaml:"organizationId"`
RevisionID uint32 `yaml:"revisionId"`
RevisionedAPIID string `yaml:"revisionedApiId"`
}

// APIYaml is a wrapper struct for YAML representation of an API.
Expand All @@ -108,5 +113,6 @@ type APIArtifact struct {
EnvConfig string `json:"envConfig"`
Swagger string `json:"swagger"`
DeploymentDescriptor string `json:"deploymentDescriptor"`
ClientCerts string `json:"clientCert"`
RevisionID uint32 `json:"revisionId"`
}
30 changes: 22 additions & 8 deletions apim-apk-agent/pkg/transformer/apk_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,29 @@

package transformer

// AdditionalProperty stores the custom properties set by the user for a particular API
type AdditionalProperty struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
}

// Certificate struct stores the the alias and the name for a particular mTLS configuration
type Certificate struct {
Name string `json:"name"`
Key string `json:"key"`
}

// AuthConfiguration represents the security configurations made for the API security
type AuthConfiguration struct {
Required string `yaml:"required,omitempty"`
AuthType string `yaml:"authType,omitempty"`
HeaderName string `yaml:"headerName,omitempty"`
SendTokenUpStream bool `yaml:"sendTokenToUpstream,omitempty"`
Enabled bool `yaml:"enabled,omitempty"`
QueryParamName string `yaml:"queryParamName,omitempty"`
HeaderEnabled bool `yaml:"headerEnable,omitempty"`
queryParamEnable bool `yaml:"queryParamEnable,omitempty"`
Required string `yaml:"required,omitempty"`
AuthType string `yaml:"authType,omitempty"`
HeaderName string `yaml:"headerName,omitempty"`
SendTokenUpStream bool `yaml:"sendTokenToUpstream,omitempty"`
Enabled bool `yaml:"enabled,omitempty"`
QueryParamName string `yaml:"queryParamName,omitempty"`
HeaderEnabled bool `yaml:"headerEnable,omitempty"`
queryParamEnable bool `yaml:"queryParamEnable,omitempty"`
Certificates []Certificate `yaml:"certificates,omitempty"`
}

// Endpoint represents an API endpoint.
Expand Down Expand Up @@ -79,4 +92,5 @@ type API struct {
Operations *[]Operation `yaml:"operations,omitempty"`
Authentication *[]AuthConfiguration `yaml:"authentication,omitempty"`
CorsConfig *CORSConfiguration `yaml:"corsConfiguration,omitempty"`
AdditionalProperties *[]AdditionalProperty `yaml:"additionalProperties,omitempty"`
}
40 changes: 40 additions & 0 deletions apim-apk-agent/pkg/transformer/client_cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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

// APIIdentifier holds information about an API associated for a given client certificate
type APIIdentifier struct {
ProviderName string `json:"providerName"`
APIName string `json:"apiName"`
Version string `json:"version"`
UUID string `json:"uuid"`
ID int `json:"id"`
}

// ClientCert holds the data belongs to a single client certificate configuration
type ClientCert struct {
Alias string `json:"alias"`
Certificate string `json:"certificate"`
TierName string `json:"tierName"`
APIIdentifier APIIdentifier `json:"apiIdentifier"`
}

// CertDescriptor contains data related to one or more client certificates for an API
type CertDescriptor struct {
CertData []ClientCert `json:"data"`
}
2 changes: 2 additions & 0 deletions apim-apk-agent/pkg/transformer/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const (
k8sOrganizationField = "organization"
k8sHostnamesField = "hostnames"
k8sLabelsField = "labels"
k8RevisionField = "revisionID"
k8APIUuidField = "apiUUID"

// K8s CRD values
k8sKindHTTPRoute = "HTTPRoute"
Expand Down
113 changes: 91 additions & 22 deletions apim-apk-agent/pkg/transformer/transformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import (
)

// GenerateAPKConf will Generate the mapped .apk-conf file for a given API Project zip
func GenerateAPKConf(APIJson string) (string, uint32, error) {
func GenerateAPKConf(APIJson string, clientCerts string) (string, string, uint32, error) {

apk := &API{}

Expand All @@ -54,8 +54,8 @@ func GenerateAPKConf(APIJson string) (string, uint32, error) {
apiYamlError := json.Unmarshal([]byte(APIJson), &apiYaml)

if apiYamlError != nil {
logger.LoggerTransformer.Error("Error while unmarshalling api yaml", apiYamlError)
return "", 0, apiYamlError
logger.LoggerTransformer.Error("Error while unmarshalling api.json content", apiYamlError)
return "", "null", 0, apiYamlError
}

apiYamlData := apiYaml.Data
Expand Down Expand Up @@ -98,30 +98,44 @@ func GenerateAPKConf(APIJson string) (string, uint32, error) {
Endpoint: apiYamlData.EndpointConfig.ProductionEndpoints.URL},
}

//TODO: Currently only the oauth2 is considered when mapping. In further improvemets, this logic should be
// changed.
if StringExists("oauth2", apiYamlData.SecuritySchemes) && apiYamlData.AuthorizationHeader == "Authorization" {
var authConfigs []AuthConfiguration
authConfig := AuthConfiguration{
Required: "mandatory",
AuthType: "OAuth2",
HeaderName: apiYamlData.AuthorizationHeader,
Enabled: false,
}
var certList CertDescriptor
certAvailable := false

authConfigs = append(authConfigs, authConfig)
apk.Authentication = &authConfigs
if clientCerts != "" {
certErr := json.Unmarshal([]byte(clientCerts), &certList)
if certErr != nil {
logger.LoggerTransformer.Errorf("Error while unmarshalling client_cert.json content: ", apiYamlError)
return "", "null", 0, certErr
}
certAvailable = true
} else {
logger.LoggerTransformer.Info("Alert:client_cert.json empty or not exist for the given zip.")
}

authConfigList := mapAuthConfigs(apiYamlData.AuthorizationHeader, apiYamlData.SecuritySchemes, certAvailable, certList)
apk.Authentication = &authConfigList

apk.CorsConfig = &apiYamlData.CORSConfiguration

aditionalProperties := make([]AdditionalProperty, len(apiYamlData.AdditionalProperties))

for i, property := range apiYamlData.AdditionalProperties {
prop := &AdditionalProperty{
Name: property.Name,
Value: property.Value,
}
aditionalProperties[i] = *prop
}

apk.AdditionalProperties = &aditionalProperties

c, marshalError := yaml.Marshal(apk)

if marshalError != nil {
logger.LoggerTransformer.Error("Error while marshalling apk yaml", marshalError)
return "", 0, marshalError
return "", "null", 0, marshalError
}
return string(c), apiYamlData.RevisionID, nil
return string(c), apiYamlData.RevisionedAPIID, apiYamlData.RevisionID, nil
}

// getAPIType will be selecting the appropriate API type need to be added in the apk-conf
Expand Down Expand Up @@ -177,9 +191,51 @@ func getReqAndResInterceptors(reqPolicyCount int, resPolicyCount int) (*[]Operat
return &reqInterceptor, &resInterceptor
}

// mapAuthConfigs will take the security schemes as the parameter and will return the mapped auth configs to be
// added into the apk-conf
func mapAuthConfigs(authHeader string, secSchemes []string, certAvailable bool, certList CertDescriptor) []AuthConfiguration {
var authConfigs []AuthConfiguration
if StringExists("oauth2", secSchemes) {
var newConfig AuthConfiguration
newConfig.AuthType = "OAuth2"
newConfig.Enabled = true
newConfig.HeaderName = authHeader
if StringExists("oauth_basic_auth_api_key_mandatory", secSchemes) {
newConfig.Required = "mandatory"
} else {
newConfig.Required = "optional"
}

authConfigs = append(authConfigs, newConfig)
}
if StringExists("mutualssl", secSchemes) && certAvailable {
var newConfig AuthConfiguration
newConfig.AuthType = "mTLS"
newConfig.Enabled = true
if StringExists("mutualssl_mandatory", secSchemes) {
newConfig.Required = "mandatory"
} else {
newConfig.Required = "optional"
}

clientCerts := make([]Certificate, len(certList.CertData))

for i, cert := range certList.CertData {
prop := &Certificate{
Name: cert.Alias,
Key: cert.Certificate,
}
clientCerts[i] = *prop
}
newConfig.Certificates = clientCerts
authConfigs = append(authConfigs, newConfig)
}
return authConfigs
}

// GenerateUpdatedCRs 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 GenerateUpdatedCRs(apkConf string, apiDefinition string, k8ResourceGenEndpoint string, deploymentDescriptor *DeploymentDescriptor, apiFileName string) (*bytes.Buffer, error) {
func GenerateUpdatedCRs(apkConf string, apiDefinition string, k8ResourceGenEndpoint string, deploymentDescriptor *DeploymentDescriptor, apiFileName string, apiID string, revisionID string) (*bytes.Buffer, error) {
if apkConf == "" {
logger.LoggerTransformer.Error("Empty apk-conf parameter provided. Unable to generate CRDs.")
return nil, errors.New("Error: APK-Conf can't be empty")
Expand Down Expand Up @@ -255,7 +311,7 @@ func GenerateUpdatedCRs(apkConf string, apiDefinition string, k8ResourceGenEndpo
if deployment.APIFile == apiFileName {
for _, environment := range *deployment.Environments {

modifiedZip, err := transformCRD(body, environment.Vhost, deployment.OrganizationID)
modifiedZip, err := transformCRD(body, environment.Vhost, deployment.OrganizationID, apiID, revisionID)

if err != nil {
logger.LoggerTransformer.Error("Unable to transform the initial CRDs:", err)
Expand Down Expand Up @@ -291,7 +347,7 @@ func GenerateUpdatedCRs(apkConf string, apiDefinition string, k8ResourceGenEndpo
}

// transformCRD converts the APK CRDs and returns the modified CRDs with modified
func transformCRD(crdZip []byte, vHost string, organization string) ([]byte, error) {
func transformCRD(crdZip []byte, vHost string, organization string, apiID string, revisionID string) ([]byte, error) {
zipReader, err := zip.NewReader(bytes.NewReader(crdZip), int64(len(crdZip)))
if err != nil {
logger.LoggerTransformer.Fatal(err)
Expand All @@ -315,7 +371,7 @@ func transformCRD(crdZip []byte, vHost string, organization string) ([]byte, err
}

_ = apkCRDFileBytes // this is unzipped file bytes
yamlCrd, err := generateAPKCrdsFromYaml(apkCRDFileBytes, organization, vHost, namespace)
yamlCrd, err := generateAPKCrdsFromYaml(apkCRDFileBytes, organization, vHost, namespace, apiID, revisionID)
if err != nil {
logger.LoggerTransformer.Error("Error occured while retrieving the modified CRDs", err)
return nil, err
Expand Down Expand Up @@ -349,7 +405,7 @@ func transformCRD(crdZip []byte, vHost string, organization string) ([]byte, err

// generateAPKCrdsFromYaml processes the returned APK CRD yaml, replaces the vhost, adds the organization
// and namespace and returns the json
func generateAPKCrdsFromYaml(crdYaml []byte, orgUUID, vhost string, namespace string) ([]byte, error) {
func generateAPKCrdsFromYaml(crdYaml []byte, orgUUID, vhost string, namespace string, apiID string, revisionID string) ([]byte, error) {
var crdYml map[interface{}]interface{}
unMarshalErr := yaml.Unmarshal(crdYaml, &crdYml)

Expand All @@ -359,6 +415,7 @@ func generateAPKCrdsFromYaml(crdYaml []byte, orgUUID, vhost string, namespace st
replaceVhost(crdYml, vhost)
addOrganization(crdYml, orgUUID)
addNamespace(crdYml, namespace)
addRevisionAndAPIUUID(crdYml, apiID, revisionID)

processdCrdYml := convertMap(crdYml)

Expand Down Expand Up @@ -445,6 +502,18 @@ func addNamespace(inputMap map[interface{}]interface{}, namespace string) {
}
}

// addRevisionAndAPIUUID will add the API ID and the revision field attributes to the API CR
func addRevisionAndAPIUUID(inputMap map[interface{}]interface{}, apiID string, revisionID string) {
if kind, ok := inputMap[k8sKindField].(string); ok && kind == k8sKindAPI {
if metadata, ok := inputMap[k8sMetadataField].(map[interface{}]interface{}); ok {
if labels, ok := metadata[k8sLabelsField].(map[interface{}]interface{}); ok {
labels[k8APIUuidField] = apiID
labels[k8RevisionField] = revisionID
}
}
}
}

// generateSHA1Hash returns the SHA1 hash for the given string
func generateSHA1Hash(input string) string {
h := sha1.New() /* #nosec */
Expand Down
Loading

0 comments on commit e83c1af

Please sign in to comment.