Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APIGOV-26363 - fix several issues related to proxy mode discovery #149

Merged
merged 5 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion client/pkg/apigee/models/model_ssl_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type SslInfo struct {
// Flag that specifies whether to enable one-way TLS/SSL. You must have defined a keystore containing the cert and private key. **For Edge for Public Cloud**: * You must have a cert signed by a trusted entity, such as Symantec or VeriSign. You cannot use a self-signed cert, or leaf certificates signed by a self-signed CA. * If your existing virtual host is configured to use a port other than `443`, you cannot change the TLS setting. That means you cannot change the TLS setting from enabled to disabled, or from disabled to enabled.
Enabled string `json:"enabled,omitempty"`
// Flag that specifies whether to ignore TLS certificate errors. This is similar to the `-k` option to curl. This option is valid when configuring TLS for Target Servers and Target Endpoints, and when configuring virtual hosts that use 2-way TLS. When used with a target endpoint/target server, if the backend system uses SNI and returns a cert with a subject Distinguished Name (DN) that does not match the hostname, there is no way to ignore the error and the connection fails.
IgnoreValidationErrors string `json:"ignoreValidationErrors,omitempty"`
IgnoreValidationErrors bool `json:"ignoreValidationErrors,omitempty"`
// Alias specified when you uploaded the cert and private key to the keystore. You must specify the alias name literally; you cannot use a reference. See <a href=\"https://docs.apigee.com/api-platform/system-administration/options-configuring-tls\">Options for configuring TLS</a> for more.
KeyAlias string `json:"keyAlias,omitempty"`
// Name of the keystore on Edge. Apigee recommends that you use a reference to specify the keystore name so that you can change the keystore without having to restart Routers. See <a href=\"https://docs.apigee.com/api-platform/system-administration/options-configuring-tls\">Options for configuring TLS</a> for more.
Expand Down
63 changes: 62 additions & 1 deletion client/pkg/apigee/proxy.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
package apigee

import (
"archive/zip"
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"

"github.com/Axway/agents-apigee/client/pkg/apigee/models"
)

// proxyXML
type proxyXML struct {
XMLName xml.Name `xml:"ProxyEndpoint"`
HTTPProxyConnection *HTTPProxyConnection `xml:"HTTPProxyConnection"`
}

// HTTPProxyConnection
type HTTPProxyConnection struct {
XMLName xml.Name `xml:"HTTPProxyConnection"`
BasePath string `xml:"BasePath"`
VirtualHost string `xml:"VirtualHost"`
}

// Products
type Proxies []string

Expand Down Expand Up @@ -47,7 +64,7 @@ func (a *ApigeeClient) GetProxy(proxyName string) (*models.ApiProxy, error) {
return proxy, nil
}

// GetProxy - get a proxy with a name
// GetRevision - get a revision of a proxy with a name
func (a *ApigeeClient) GetRevision(proxyName, revision string) (*models.ApiProxyRevision, error) {
response, err := a.newRequest(http.MethodGet, fmt.Sprintf("%s/apis/%s/revisions/%s", a.orgURL, proxyName, revision),
WithDefaultHeaders(),
Expand All @@ -65,6 +82,50 @@ func (a *ApigeeClient) GetRevision(proxyName, revision string) (*models.ApiProxy
return proxyRevision, nil
}

// GetRevisionBundle - get a revision bundle of a proxy with a name
func (a *ApigeeClient) GetRevisionConnectionType(proxyName, revision string) (*HTTPProxyConnection, error) {
response, err := a.newRequest(http.MethodGet, fmt.Sprintf("%s/apis/%s/revisions/%s", a.orgURL, proxyName, revision),
WithDefaultHeaders(),
WithQueryParam("format", "bundle"),
).Execute()
if err != nil {
return nil, err
}

// response is a zip file, lets
jcollins-axway marked this conversation as resolved.
Show resolved Hide resolved
zipReader, err := zip.NewReader(bytes.NewReader(response.Body), int64(len(response.Body)))
if err != nil {
return nil, err
}

// Read all the files from the zip archive
var fileBytes []byte
for _, zipFile := range zipReader.File {
if zipFile.Name != "apiproxy/proxies/default.xml" {
continue
}
fileBytes, err = readZipFile(zipFile)
if err != nil {
return nil, err
}
break
}

jcollins-axway marked this conversation as resolved.
Show resolved Hide resolved
data := &proxyXML{}
xml.Unmarshal(fileBytes, data)

return data.HTTPProxyConnection, nil
}

func readZipFile(zf *zip.File) ([]byte, error) {
f, err := zf.Open()
if err != nil {
return nil, err
}
defer f.Close()
return io.ReadAll(f)
}

// GetProxy - get a proxy with a name
func (a *ApigeeClient) GetRevisionResourceFile(proxyName, revision, resourceType, resourceName string) ([]byte, error) {
response, err := a.newRequest(http.MethodGet, fmt.Sprintf("%s/apis/%s/revisions/%s/resourcefiles/%s/%s", a.orgURL, proxyName, revision, resourceType, resourceName),
Expand Down
1 change: 1 addition & 0 deletions client/scripts/apigee_generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ sed -i -r 's/Timestamp.string/Timestamp int64/g' /codegen/output/model_metrics_v
# replace the model_metrics_metrics.go file with the template for the custom unmarshal
cp ./model_metrics_metrics.tmpl /codegen/output/model_metrics_metrics.go
cp ./model_virtual_host.tmpl /codegen/output/model_virtual_host.go
cp ./model_ssl_info.tmpl /codegen/output/model_ssl_info.go

rm ./openapitools.json
22 changes: 22 additions & 0 deletions client/scripts/model_ssl_info.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Modified after generation
package models

// SslInfo SSL information.
type SslInfo struct {
// **Edge for Private Cloud version 4.15.07 and earlier only.** Specifies the ciphers supported by the virtual host. If no ciphers are specified, then all ciphers available for the JVM will be permitted. To restrict ciphers, add the following elements: `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` and `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`
Ciphers []string `json:"ciphers,omitempty"`
// Flag that specifies whether to enable two-way, or client, TLS between Edge (server) and the app (client) making the request. Enabling two-way TLS requires that you set up a truststore on Edge that contains the cert from the TLS client.
ClientAuthEnabled string `json:"clientAuthEnabled,omitempty"`
// Flag that specifies whether to enable one-way TLS/SSL. You must have defined a keystore containing the cert and private key. **For Edge for Public Cloud**: * You must have a cert signed by a trusted entity, such as Symantec or VeriSign. You cannot use a self-signed cert, or leaf certificates signed by a self-signed CA. * If your existing virtual host is configured to use a port other than `443`, you cannot change the TLS setting. That means you cannot change the TLS setting from enabled to disabled, or from disabled to enabled.
Enabled string `json:"enabled,omitempty"`
// Flag that specifies whether to ignore TLS certificate errors. This is similar to the `-k` option to curl. This option is valid when configuring TLS for Target Servers and Target Endpoints, and when configuring virtual hosts that use 2-way TLS. When used with a target endpoint/target server, if the backend system uses SNI and returns a cert with a subject Distinguished Name (DN) that does not match the hostname, there is no way to ignore the error and the connection fails.
IgnoreValidationErrors bool `json:"ignoreValidationErrors,omitempty"`
// Alias specified when you uploaded the cert and private key to the keystore. You must specify the alias name literally; you cannot use a reference. See <a href=\"https://docs.apigee.com/api-platform/system-administration/options-configuring-tls\">Options for configuring TLS</a> for more.
KeyAlias string `json:"keyAlias,omitempty"`
// Name of the keystore on Edge. Apigee recommends that you use a reference to specify the keystore name so that you can change the keystore without having to restart Routers. See <a href=\"https://docs.apigee.com/api-platform/system-administration/options-configuring-tls\">Options for configuring TLS</a> for more.
KeyStore string `json:"keyStore,omitempty"`
// **Edge for Private Cloud version 4.15.07 and earlier only.** Specifies the protocols supported by the virtual host. If no protocols are specified, then all protocols available for the JVM will be permitted. To restrict protocols, add the following elements: `TLSv1`, `TLSv1.2`, and `SSLv2Hello`
Protocols []string `json:"protocols,omitempty"`
// Name of the truststore on Edge that contains the certificate or certificate chain used for two-way TLS. Required if `clientAuthEnabled` is `true`. Apigee recommends that you use a reference to specify the truststore name so that you can change the truststore without having to restart Routers. See <a href=\"https://docs.apigee.com/api-platform/system-administration/options-configuring-tls\">Options for configuring TLS</a> for more.
TrustStore string `json:"trustStore,omitempty"`
}
7 changes: 7 additions & 0 deletions discovery/pkg/apigee/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package apigee
import (
"fmt"
"strings"
"sync"
"time"

"github.com/Axway/agent-sdk/pkg/apic"
Expand All @@ -12,6 +13,7 @@ import (
type agentCache struct {
cache cache.Cache
specEndpointToKeys map[string][]specCacheItem
mutex *sync.Mutex
}

type specCacheItem struct {
Expand All @@ -25,6 +27,7 @@ func newAgentCache() *agentCache {
return &agentCache{
cache: cache.New(),
specEndpointToKeys: make(map[string][]specCacheItem),
mutex: &sync.Mutex{},
}
}

Expand All @@ -43,6 +46,8 @@ func (a *agentCache) AddSpecToCache(id, path, name string, modDate time.Time, en
a.cache.SetWithSecondaryKey(specPrimaryKey(name), path, item)
a.cache.SetSecondaryKey(specPrimaryKey(name), strings.ToLower(name))
a.cache.SetSecondaryKey(specPrimaryKey(name), id)
a.mutex.Lock()
defer a.mutex.Unlock()
for _, ep := range endpoints {
if _, found := a.specEndpointToKeys[ep]; !found {
a.specEndpointToKeys[ep] = []specCacheItem{}
Expand Down Expand Up @@ -90,6 +95,8 @@ func (a *agentCache) GetSpecWithName(name string) (*specCacheItem, error) {

// GetSpecPathWithEndpoint - returns the lat modified spec found with this endpoint
func (a *agentCache) GetSpecPathWithEndpoint(endpoint string) (string, error) {
a.mutex.Lock()
defer a.mutex.Unlock()
items, found := a.specEndpointToKeys[endpoint]
if !found {
return "", fmt.Errorf("no spec found for endpoint: %s", endpoint)
Expand Down
80 changes: 52 additions & 28 deletions discovery/pkg/apigee/pollproxiesjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type proxyClient interface {
GetAllProxies() (apigee.Proxies, error)
GetRevision(proxyName, revision string) (*models.ApiProxyRevision, error)
GetRevisionResourceFile(proxyName, revision, resourceType, resourceName string) ([]byte, error)
GetRevisionConnectionType(proxyName, revision string) (*apigee.HTTPProxyConnection, error)
GetDeployments(apiName string) (*models.DeploymentDetails, error)
GetVirtualHost(envName, virtualHostName string) (*models.VirtualHost, error)
GetSpecFile(specPath string) ([]byte, error)
Expand All @@ -52,28 +53,32 @@ type proxyCache interface {
// job that will poll for any new portals on APIGEE Edge
type pollProxiesJob struct {
jobs.Job
client proxyClient
cache proxyCache
firstRun bool
logger log.FieldLogger
specsReady jobFirstRunDone
pubLock sync.Mutex
publishFunc agent.PublishAPIFunc
workers int
running bool
runningLock sync.Mutex
client proxyClient
cache proxyCache
firstRun bool
logger log.FieldLogger
specsReady jobFirstRunDone
pubLock sync.Mutex
publishFunc agent.PublishAPIFunc
workers int
running bool
runningLock sync.Mutex
virtualHostURLs map[string]map[string][]string
lastTime int
runTime int
}

func newPollProxiesJob(client proxyClient, cache proxyCache, specsReady jobFirstRunDone, workers int) *pollProxiesJob {
job := &pollProxiesJob{
client: client,
cache: cache,
firstRun: true,
specsReady: specsReady,
logger: log.NewFieldLogger().WithComponent("pollProxies").WithPackage("apigee"),
publishFunc: agent.PublishAPI,
workers: workers,
runningLock: sync.Mutex{},
client: client,
cache: cache,
firstRun: true,
specsReady: specsReady,
logger: log.NewFieldLogger().WithComponent("pollProxies").WithPackage("apigee"),
publishFunc: agent.PublishAPI,
workers: workers,
runningLock: sync.Mutex{},
virtualHostURLs: make(map[string]map[string][]string),
}
return job
}
Expand Down Expand Up @@ -131,6 +136,7 @@ func (j *pollProxiesJob) Execute() error {

wg := sync.WaitGroup{}
wg.Add(len(allProxies))
j.runTime = j.lastTime
for _, proxyName := range allProxies {
go func() {
defer wg.Done()
Expand Down Expand Up @@ -202,6 +208,13 @@ func (j *pollProxiesJob) handleRevision(ctx context.Context, revName string) {
return
}

if revision.LastModifiedAt <= j.runTime {
return
}
if j.lastTime < revision.LastModifiedAt {
j.lastTime = revision.LastModifiedAt
}

ctx = context.WithValue(ctx, revNameField, revision)
logger = logger.WithField(revNameField.String(), revision.Revision)
addLoggerToContext(ctx, logger)
Expand Down Expand Up @@ -275,18 +288,29 @@ func (j *pollProxiesJob) getVirtualHostURLs(ctx context.Context) context.Context
revision := ctx.Value(revNameField).(*models.ApiProxyRevision)
envName := getStringFromContext(ctx, envNameField)
proxyName := getStringFromContext(ctx, proxyNameField)

// attempt to get the spec from the endpoints the revision is hosted on
allURLs := []string{}
for _, virtualHostName := range revision.Proxies {
logger := logger.WithField("virtualHostName", virtualHostName)
virtualHost, err := j.client.GetVirtualHost(envName, virtualHostName)

connection, err := j.client.GetRevisionConnectionType(proxyName, revision.Revision)
if err != nil {
logger.WithError(err).Error("could not get the revision connection type")
return context.WithValue(ctx, endpointsField, allURLs)
}

if _, ok := j.virtualHostURLs[envName]; !ok {
j.virtualHostURLs[envName] = make(map[string][]string)
}

if _, ok := j.virtualHostURLs[envName][connection.VirtualHost]; !ok {
virtualHost, err := j.client.GetVirtualHost(envName, connection.VirtualHost)
if err != nil {
logger.WithError(err).Debug("could not get virtual host details")
continue
logger.WithError(err).Error("could not get the virtual host info")
return context.WithValue(ctx, endpointsField, allURLs)
}
urls := urlsFromVirtualHost(virtualHost, proxyName)
allURLs = append(allURLs, urls...)
j.virtualHostURLs[envName][connection.VirtualHost] = urlsFromVirtualHost(virtualHost)
}

for _, url := range j.virtualHostURLs[envName][connection.VirtualHost] {
allURLs = append(allURLs, fmt.Sprintf("%s%s", url, connection.BasePath))
}

return context.WithValue(ctx, endpointsField, allURLs)
Expand Down Expand Up @@ -396,7 +420,7 @@ func (j *pollProxiesJob) buildServiceBody(ctx context.Context) (*apic.ServiceBod
}

if len(spec) == 0 {
logger.Debug("creating without a spec")
return nil, fmt.Errorf("skipping proxy creation without a spec")
}
logger.Debug("creating service body")

Expand Down
9 changes: 6 additions & 3 deletions discovery/pkg/apigee/pollproxiesjob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ func Test_pollProxiesJob(t *testing.T) {
hasOauth: true,
},
{
name: "should create proxy when no spec found but has api key policy",
hasAPIKey: true,
name: "should create proxy when no spec found",
},
{
name: "should create proxy when no spec found",
name: "should stop when no spec found but has api key policy",
},
{
name: "should stop when getting proxy revision fails",
Expand Down Expand Up @@ -206,6 +205,10 @@ func (m mockProxyClient) GetRevision(apiName, revision string) (rev *models.ApiP
return
}

func (m mockProxyClient) GetRevisionConnectionType(proxyName, revision string) (*apigee.HTTPProxyConnection, error) {
return nil, nil
}

func (m mockProxyClient) GetRevisionResourceFile(apiName, revision, resourceType, resourceName string) ([]byte, error) {
assert.Contains(m.t, proxyName, apiName)
assert.Contains(m.t, revName, revision)
Expand Down
Loading