Skip to content

Commit

Permalink
internal JWT tokens for hybrid
Browse files Browse the repository at this point in the history
always emit CRD format
change --truncate -to --rotate in provision
remove --verify-only option in provision
eliminate `token create-secret` cmd
  • Loading branch information
theganyo committed Jun 29, 2020
1 parent d3080e6 commit 6d658e8
Show file tree
Hide file tree
Showing 12 changed files with 812 additions and 952 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ coverage.txt
dist/

# Misc
/.vscode
/apigee-remote-service-cli
/config.yaml
/configmap.yaml
8 changes: 4 additions & 4 deletions cmd/bindings/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ func (b *bindings) getProducts() ([]product.APIProduct, error) {
if b.products != nil {
return b.products, nil
}
req, err := b.Client.NewRequest(http.MethodGet, "", nil)
req, err := b.ApigeeClient.NewRequest(http.MethodGet, "", nil)
if err != nil {
return nil, errors.Wrap(err, "creating request")
}
req.URL.Path = fmt.Sprintf(productsURLFormat, b.Org) // hack: negate client's base URL
req.URL.RawQuery = "expand=true"

var res product.APIResponse
resp, err := b.Client.Do(req, &res)
resp, err := b.ApigeeClient.Do(req, &res)
if err != nil {
return nil, errors.Wrap(err, "retrieving products")
}
Expand Down Expand Up @@ -264,14 +264,14 @@ func (b *bindings) updateTargetBindings(p *product.APIProduct, bindings []string
newAttrs := attrUpdate{
Attributes: attributes,
}
req, err := b.Client.NewRequest(http.MethodPost, "", newAttrs)
req, err := b.ApigeeClient.NewRequest(http.MethodPost, "", newAttrs)
if err != nil {
return err
}
path := fmt.Sprintf(productAttrPathFormat, b.Org, p.Name)
req.URL.Path = path // hack: negate client's base URL
var attrResult attrUpdate
_, err = b.Client.Do(req, &attrResult)
_, err = b.ApigeeClient.Do(req, &attrResult)
return err
}

Expand Down
244 changes: 244 additions & 0 deletions cmd/provision/legacy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// Copyright 2018 Google LLC
//
// 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 provision

import (
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/xml"
"fmt"
"io/ioutil"
rnd "math/rand"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"

"github.com/apigee/apigee-remote-service-cli/apigee"
"github.com/apigee/apigee-remote-service-cli/shared"
"github.com/pkg/errors"
"go.uber.org/multierr"
)

const (
legacyCredentialURLFormat = "%s/credential/organization/%s/environment/%s" // InternalProxyURL, org, env
analyticsURLFormat = "%s/analytics/organization/%s/environment/%s" // InternalProxyURL, org, env
legacyAnalyticURLFormat = "%s/axpublisher/organization/%s/environment/%s" // InternalProxyURL, org, env
legacyAuthProxyZip = "remote-service-legacy.zip"

// virtualHost is only necessary for legacy
virtualHostDeleteText = "<VirtualHost>secure</VirtualHost>"
virtualHostReplaceText = "<VirtualHost>default</VirtualHost>"
virtualHostReplacementFmt = "<VirtualHost>%s</VirtualHost>" // each virtualHost

internalProxyName = "edgemicro-internal"
internalProxyZip = "internal.zip"
)

func (p *provision) deployInternalProxy(replaceVirtualHosts func(proxyDir string) error, tempDir string, verbosef shared.FormatFn) error {

customizedZip, err := getCustomizedProxy(tempDir, internalProxyZip, func(proxyDir string) error {

// change server locations
calloutFile := filepath.Join(proxyDir, "policies", "Callout.xml")
bytes, err := ioutil.ReadFile(calloutFile)
if err != nil {
return errors.Wrapf(err, "reading file %s", calloutFile)
}
var callout JavaCallout
if err := xml.Unmarshal(bytes, &callout); err != nil {
return errors.Wrapf(err, "unmarshalling %s", calloutFile)
}
setMgmtURL := false
for i, cp := range callout.Properties {
if cp.Name == "REGION_MAP" {
callout.Properties[i].Value = fmt.Sprintf("DN=%s", p.RuntimeBase)
}
if cp.Name == "MGMT_URL_PREFIX" {
setMgmtURL = true
callout.Properties[i].Value = p.ManagementBase
}
}
if !setMgmtURL {
callout.Properties = append(callout.Properties,
javaCalloutProperty{
Name: "MGMT_URL_PREFIX",
Value: p.ManagementBase,
})
}

writer, err := os.OpenFile(calloutFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0)
if err != nil {
return errors.Wrapf(err, "writing file %s", calloutFile)
}
writer.WriteString(xml.Header)
encoder := xml.NewEncoder(writer)
encoder.Indent("", " ")
err = encoder.Encode(callout)
if err != nil {
return errors.Wrapf(err, "encoding xml to %s", calloutFile)
}
err = writer.Close()
if err != nil {
return errors.Wrapf(err, "closing file %s", calloutFile)
}

return replaceVirtualHosts(proxyDir)
})
if err != nil {
return err
}

return p.checkAndDeployProxy(internalProxyName, customizedZip, verbosef)
}

//check if the KVM exists, if it doesn't, create a new one and sets certs for JWT
func (p *provision) getOrCreateKVM(cred *keySecret, printf shared.FormatFn) error {

kid, keyBytes, jwksBytes, err := p.CreateJWKS(1, printf)
if err != nil {
return err
}

kvm := apigee.KVM{
Name: kvmName,
Encrypted: encryptKVM,
Entries: []apigee.Entry{
{
Name: "private_key",
Value: string(keyBytes),
},
{
Name: "jwks",
Value: string(jwksBytes),
},
{
Name: "kid",
Value: kid,
},
},
}

resp, err := p.ApigeeClient.KVMService.Create(kvm)
if err != nil && (resp == nil || resp.StatusCode != http.StatusConflict) { // http.StatusConflict == already exists
return err
}
if resp.StatusCode == http.StatusConflict {
printf("kvm %s already exists", kvmName)
return nil
}
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("creating kvm %s, status code: %v", kvmName, resp.StatusCode)
}
printf("kvm %s created", kvmName)

printf("new private key:\n%s", string(keyBytes))
printf("new jwks:\n%s", string(jwksBytes))

return nil
}

// hash for key and secret
func newHash() string {
// use crypto seed
var seed int64
binary.Read(rand.Reader, binary.BigEndian, &seed)
rnd.Seed(seed)

t := time.Now()
h := sha256.New()
h.Write([]byte(t.String() + string(rnd.Int())))
str := hex.EncodeToString(h.Sum(nil))
return str
}

func (p *provision) createLegacyCredential(printf shared.FormatFn) (*keySecret, error) {
printf("creating credential...")
cred := &keySecret{
Key: newHash(),
Secret: newHash(),
}

credentialURL := fmt.Sprintf(legacyCredentialURLFormat, p.InternalProxyURL, p.Org, p.Env)

req, err := p.ApigeeClient.NewRequest(http.MethodPost, credentialURL, cred)
if err != nil {
return nil, err
}
req.URL, err = url.Parse(credentialURL) // override client's munged URL
if err != nil {
return nil, err
}

resp, err := p.ApigeeClient.Do(req, nil)
if err != nil {
return nil, err
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("creating credential, status: %d", resp.StatusCode)
}
printf("credential created")
return cred, nil
}

// verify POST internalProxyURL/analytics/organization/%s/environment/%s
// verify POST internalProxyURL/quotas/organization/%s/environment/%s
func (p *provision) verifyInternalProxy(client *http.Client, printf shared.FormatFn) error {
var verifyErrors error

var req *http.Request
var err error
var res *http.Response
if p.IsOPDK {
analyticsURL := fmt.Sprintf(legacyAnalyticURLFormat, p.InternalProxyURL, p.Org, p.Env)
req, err = http.NewRequest(http.MethodPost, analyticsURL, strings.NewReader("{}"))
} else {
analyticsURL := fmt.Sprintf(analyticsURLFormat, p.InternalProxyURL, p.Org, p.Env)
req, err = http.NewRequest(http.MethodGet, analyticsURL, nil)
q := req.URL.Query()
q.Add("tenant", fmt.Sprintf("%s~%s", p.Org, p.Env))
q.Add("relative_file_path", "fake")
q.Add("file_content_type", "application/x-gzip")
q.Add("encrypt", "true")
req.URL.RawQuery = q.Encode()
}
if err != nil {
res, err = client.Do(req)
if res != nil {
defer res.Body.Close()
}
}
if err != nil {
verifyErrors = multierr.Append(verifyErrors, err)
}

return verifyErrors
}

// JavaCallout must be capitalized to ensure correct generation
type JavaCallout struct {
Name string `xml:"name,attr"`
DisplayName, ClassName, ResourceURL string
Properties []javaCalloutProperty `xml:"Properties>Property"`
}

type javaCalloutProperty struct {
Name string `xml:"name,attr"`
Value string `xml:",chardata"`
}
Loading

0 comments on commit 6d658e8

Please sign in to comment.