...and also the 'edge' bits.

This commit is contained in:
Michael Quigley
2019-12-03 12:17:40 -05:00
parent 58fdd03351
commit 9debc34cc6
395 changed files with 54286 additions and 6 deletions
+5 -5
View File
@@ -45,14 +45,14 @@ running with Edge functionality enabled.
If you wish to start the controller with the Ziti Fabric and Ziti Edge enabled:
```
ziti-controller run $GOPATH/src/bitbucket.org/netfoundry/ziti/fabric/etc/ctrl.with.edge.yml
ziti-controller run $GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/ctrl.with.edge.yml
```
If you wish to start the Ziti Fabric standalone:
```
ziti-controller run $GOPATH/src/bitbucket.org/netfoundry/ziti/fabric/etc/ctrl.yml
ziti-controller run $GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/ctrl.yml
```
Please note that if you start the controller without the Ziti Edge enabled, the Ziti SDK, and edge router functionality
@@ -80,7 +80,7 @@ Where `XXX` is replaced with `001` through `004`.
Run:
```
ziti-router run $GOPATH/src/bitbucket.org/netfoundry/ziti/fabric/etc/XXX.yml
ziti-router run $GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/XXX.yml
```
Where `XXX` is replaced with `001` through `004`.
@@ -172,7 +172,7 @@ edge routers via `GET /edge-routers`. The response from retrieving a edge router
### Enroll the Edge Router
```
ziti-router enroll --jwt <path to enrollment.jwt> $GOPATH/src/bitbucket.org/netfoundry/ziti/fabric/etc/edge.router.yml
ziti-router enroll --jwt <path to enrollment.jwt> $GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/edge.router.yml
```
...where `path to enrollment.jwt` is the enrollment JWT for the edge router.
@@ -180,7 +180,7 @@ ziti-router enroll --jwt <path to enrollment.jwt> $GOPATH/src/bitbucket.org/netf
### Start Ziti Edge Router
```
ziti-router run $GOPATH/src/bitbucket.org/netfoundry/ziti/fabric/etc/edge.router.yml
ziti-router run $GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/edge.router.yml
```
# Further Exploration
+8
View File
@@ -0,0 +1,8 @@
cache/*.jwt
cache/*.pem
cache/*.key
cache/*.cert
cache/*.jwt
cache/*.json
ignore.*
+50
View File
@@ -0,0 +1,50 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 apierror
import "encoding/json"
type ApiError struct {
Code string `json:"code"`
Message string `json:"message"`
Status int `json:"-"`
Cause error `json:"cause"`
AppendCause bool `json:"-"`
}
func (e ApiError) Error() string {
s := e.Code + ": " + e.Message
if e.Cause != nil && e.AppendCause {
s = s + ": " + e.Cause.Error()
}
return s
}
type GenericCauseError struct {
Message string
DataMap map[string]interface{}
}
func (e GenericCauseError) Error() string {
return e.Message
}
func (e *GenericCauseError) MarshalJSON() ([]byte, error) {
return json.Marshal(e.DataMap)
}
+99
View File
@@ -0,0 +1,99 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 apierror
import (
"encoding/json"
"fmt"
"strings"
)
const (
maxFieldErrorValueLength = 64
)
type BodyParseTypeError struct {
Near string `json:"near"`
ExpectedType string `json:"expectedType"`
EncounteredType string `json:"encounteredType"`
Offset int64 `json:"totalOffset"`
Line int64 `json:"line"`
}
func (e BodyParseTypeError) Error() string {
return fmt.Sprintf("Expected type %s, but encountered %s near %s", e.ExpectedType, e.ExpectedType, e.Near)
}
func NewBodyParseTypeError(e *json.UnmarshalTypeError, body string) *BodyParseTypeError {
nearStart := e.Offset - 10
nearEnd := e.Offset
if nearStart < 0 {
nearEnd = 5
}
near := body[nearStart:nearEnd]
bodyToError := body[:e.Offset]
line := strings.Count(bodyToError, "\n") + 1
return &BodyParseTypeError{
ExpectedType: e.Type.String(),
EncounteredType: e.Value,
Near: near,
Offset: e.Offset,
Line: int64(line),
}
}
type BodyParseSyntaxError struct {
Near string `json:"near"`
Offset int64 `json:"totalOffset"`
Line int64 `json:"line"`
}
func (e BodyParseSyntaxError) Error() string {
return fmt.Sprintf("Invalid syntax on line %d", e.Line)
}
func NewBodyParseSyntaxError(e *json.SyntaxError, body string) *BodyParseSyntaxError {
nearStart := e.Offset - 10
nearEnd := e.Offset
if nearStart < 0 {
nearStart = 0
nearEnd = 5
}
if nearEnd > int64(len(body)) {
nearEnd = int64(len(body))
}
near := body[nearStart:nearEnd]
bodyToError := body[:e.Offset]
line := strings.Count(bodyToError, "\n") + 1
return &BodyParseSyntaxError{
Near: near,
Offset: e.Offset,
Line: int64(line),
}
}
+43
View File
@@ -0,0 +1,43 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 apierror
import "fmt"
type FieldError struct {
Reason string `json:"message"`
FieldName string `json:"field"`
FieldValue interface{} `json:"value"`
}
func (fe FieldError) Error() string {
v := fe.FieldValue
if s, ok := fe.FieldValue.(string); ok {
if len(s) > maxFieldErrorValueLength {
v = s[0:maxFieldErrorValueLength] + "..."
}
}
return fmt.Sprintf("the value '%v' for '%s' is invalid: %s", v, fe.FieldName, fe.Reason)
}
func NewFieldError(reason, name string, value interface{}) *FieldError {
return &FieldError{
Reason: reason,
FieldName: name,
FieldValue: value,
}
}
+284
View File
@@ -0,0 +1,284 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 apierror
import (
"github.com/xeipuuv/gojsonschema"
"net/http"
)
func NewNotFound() *ApiError {
return &ApiError{
Code: NotFoundCode,
Message: NotFoundMessage,
Status: NotFoundStatus,
}
}
func NewForbidden() *ApiError {
return &ApiError{
Code: ForbiddenCode,
Message: ForbiddenMessage,
Status: ForbiddenStatus,
}
}
func NewUnhandled() *ApiError {
return &ApiError{
Code: UnhandledCode,
Message: UnhandledMessage,
Status: UnhandledStatus,
}
}
func NewCouldNotParseBody() *ApiError {
return &ApiError{
Code: CouldNotParseBodyCode,
Message: CouldNotParseBodyMessage,
Status: CouldNotParseBodyStatus,
}
}
func NewInvalidContentType(contentType string) *ApiError {
return &ApiError{
Code: InvalidContentTypeCode,
Message: InvalidContentTypeMessage + ": " + contentType,
Status: InvalidContentTypeStatus,
}
}
func NewCouldNotReadBody() *ApiError {
return &ApiError{
Code: CouldNotReadBodyCode,
Message: CouldNotReadBodyMessage,
Status: CouldNotReadBodyStatus,
}
}
func NewInvalidField() *ApiError {
return &ApiError{
Code: InvalidFieldCode,
Message: InvalidFieldMessage,
Status: InvalidFieldStatus,
}
}
func NewEntityCanNotBeDeleted() *ApiError {
return &ApiError{
Code: EntityCanNotBeDeletedCode,
Message: EntityCanNotBeDeletedMessage,
Status: EntityCanNotBeDeletedStatus,
}
}
func NewInvalidAuth() *ApiError {
return &ApiError{
Code: InvalidAuthCode,
Message: InvalidAuthMessage,
Status: InvalidAuthStatus,
}
}
func NewInvalidAuthMethod() *ApiError {
return &ApiError{
Code: InvalidAuthMethodCode,
Message: InvalidAuthMethodMessage,
Status: InvalidAuthMethodStatus,
}
}
func NewEnrollmentExpired() *ApiError {
return &ApiError{
Code: EnrollmentExpiredCode,
Message: EnrollmentExpiredMessage,
Status: EnrollmentExpiredStatus,
}
}
func NewCouldNotProcessCsr() *ApiError {
return &ApiError{
Code: CouldNotProcessCsrCode,
Message: CouldNotProcessCsrMessage,
Status: CouldNotProcessCsrStatus,
}
}
func NewEnrollmentCaNoLongValid() *ApiError {
return &ApiError{
Code: EnrollmentCaNoLongValidCode,
Message: EnrollmentCaNoLongValidMessage,
Status: EnrollmentCaNoLongValidStatus,
}
}
func NewEnrollmentNoValidCas() *ApiError {
return &ApiError{
Code: EnrollmentNoValidCasCode,
Message: EnrollmentNoValidCasMessage,
Status: EnrollmentNoValidCasStatus,
}
}
func NewInvalidEnrollmentToken() *ApiError {
return &ApiError{
Code: InvalidEnrollmentTokenCode,
Message: InvalidEnrollmentTokenMessage,
Status: InvalidEnrollmentTokenStatus,
}
}
func NewInvalidEnrollMethod() *ApiError {
return &ApiError{
Code: InvalidEnrollMethodCode,
Message: InvalidEnrollMethodMessage,
Status: InvalidEnrollMethodStatus,
}
}
func NewInvalidFilter() *ApiError {
return &ApiError{
Code: InvalidFilterCode,
Message: InvalidFilterMessage,
Status: InvalidFilterStatus,
}
}
func NewInvalidPagination() *ApiError {
return &ApiError{
Code: InvalidPaginationCode,
Message: InvalidPaginationMessage,
Status: InvalidPaginationStatus,
}
}
func NewNoEdgeRoutersAvailable() *ApiError {
return &ApiError{
Code: NoEdgeRoutersAvailableCode,
Message: NoEdgeRoutersAvailableMessage,
Status: NoEdgeRoutersAvailableStatus,
}
}
func NewInvalidSort() *ApiError {
return &ApiError{
Code: InvalidSortCode,
Message: InvalidSortMessage,
Status: InvalidSortStatus,
}
}
func NewCouldNotValidate() *ApiError {
return &ApiError{
Code: CouldNotValidateCode,
Message: CouldNotValidateMessage,
Status: CouldNotValidateStatus,
}
}
func NewUnauthorized() *ApiError {
return &ApiError{
Code: UnauthorizedCode,
Message: UnauthorizedMessage,
Status: UnauthorizedStatus,
}
}
func NewCouldNotParseX509FromDer() *ApiError {
return &ApiError{
Code: CouldNotParseX509FromDerCode,
Message: CouldNotParseX509FromDerMessage,
Status: CouldNotParseX509FromDerStatus,
}
}
func NewCertFailedValidation() *ApiError {
return &ApiError{
Code: CertFailedValidationCode,
Message: CertFailedValidationMessage,
Status: CertFailedValidationStatus,
}
}
func NewCertInUse() *ApiError {
return &ApiError{
Code: CertInUseCode,
Message: CertInUseMessage,
Status: CertInUseStatus,
}
}
func NewCaAlreadyVerified() *ApiError {
return &ApiError{
Code: CaAlreadyVerifiedCode,
Message: CaAlreadyVerifiedMessage,
Status: CaAlreadyVerifiedStatus,
}
}
func NewExpectedPemBlockCertificate() *ApiError {
return &ApiError{
Code: ExpectedPemBlockCertificateCode,
Message: ExpectedPemBlockCertificateMessage,
Status: ExpectedPemBlockCertificateStatus,
}
}
func NewCouldNotParseDerBlock() *ApiError {
return &ApiError{
Code: CouldNotParseDerBlockCode,
Message: CouldNotParseDerBlockMessage,
Status: CouldNotParseDerBlockStatus,
}
}
func NewCouldNotParsePem() *ApiError {
return &ApiError{
Code: CouldNotParsePemCode,
Message: CouldNotParsePemMessage,
Status: CouldNotParsePemStatus,
}
}
func NewInvalidCommonName() *ApiError {
return &ApiError{
Code: InvalidCommonNameCode,
Message: InvalidCommonNameMessage,
Status: InvalidCommonNameStatus,
}
}
func NewFailedCertificateValidation() *ApiError {
return &ApiError{
Code: FailedCertificateValidationCode,
Message: FailedCertificateValidationMessage,
Status: FailedCertificateValidationStatus,
}
}
func NewCertificateIsNotCa() *ApiError {
return &ApiError{
Code: CertificateIsNotCaCode,
Message: CertificateIsNotCaMessage,
Status: CertificateIsNotCaStatus,
}
}
func NewValidationError(err gojsonschema.ResultError) *ValidationError {
return &ValidationError{
Field: err.Field(),
Type: err.Type(),
Value: err.Value(),
Message: err.String(),
Details: err.Details(),
}
}
func NewField(fieldError *FieldError) *ApiError {
return &ApiError{
Code: InvalidFieldCode,
Message: InvalidFieldMessage,
Status: http.StatusBadRequest,
Cause: fieldError,
AppendCause: true,
}
}
func NewInvalidUuid(val string) *ApiError {
return &ApiError{
Code: InvalidUuidCode,
Message: InvalidUuidMessage,
Status: InvalidUuidStatus,
Cause: &GenericCauseError{
Message: "invalid uuid",
DataMap: map[string]interface{}{
"uuid": val,
},
},
}
}
+30
View File
@@ -0,0 +1,30 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 apierror
import (
"encoding/json"
)
func GetJsonParseError(e error, body []byte) error {
if ute, ok := e.(*json.UnmarshalTypeError); ok {
e = NewBodyParseTypeError(ute, string(body))
} else if se, ok := e.(*json.SyntaxError); ok {
e = NewBodyParseSyntaxError(se, string(body))
}
return e
}
+160
View File
@@ -0,0 +1,160 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 apierror
import "net/http"
const (
NotFoundCode string = "NOT_FOUND"
NotFoundMessage string = "The resource requested was not found or is no longer available"
NotFoundStatus int = http.StatusNotFound
ForbiddenCode string = "FORBIDDEN"
ForbiddenMessage string = "The resource requested"
ForbiddenStatus int = http.StatusForbidden
UnhandledCode string = "UNHANDLED"
UnhandledMessage string = "An unhandled error occurred"
UnhandledStatus int = http.StatusInternalServerError
CouldNotParseBodyCode string = "COULD_NOT_PARSE_BODY"
CouldNotParseBodyMessage string = "The body of the request could not be parsed"
CouldNotParseBodyStatus int = http.StatusInternalServerError
CouldNotReadBodyCode string = "COULD_NOT_READ_BODY"
CouldNotReadBodyMessage string = "The body of the request could not be read"
CouldNotReadBodyStatus int = http.StatusInternalServerError
InvalidFieldCode string = "INVALID_FIELD"
InvalidFieldMessage string = "The field contains an invalid value"
InvalidFieldStatus int = http.StatusBadRequest
EntityCanNotBeDeletedCode string = "ENTITY_CAN_NOT_BE_DELETED"
EntityCanNotBeDeletedMessage string = "The entity requested for delete can not be deleted"
EntityCanNotBeDeletedStatus = http.StatusBadRequest
InvalidUuidCode string = "INVALID_UUID"
InvalidUuidMessage string = "The supplied UUID is invalid"
InvalidUuidStatus int = http.StatusBadRequest
InvalidContentTypeCode string = "INVALID_CONTENT_TYPE"
InvalidContentTypeMessage string = "The content type supplied is not acceptable"
InvalidContentTypeStatus int = http.StatusBadRequest
)
// specific
const (
InvalidAuthCode string = "INVALID_AUTH"
InvalidAuthMessage string = "The authentication request failed"
InvalidAuthStatus int = http.StatusUnauthorized
InvalidAuthMethodCode string = "INVALID_AUTH_METHOD"
InvalidAuthMethodMessage string = "The supplied authentication method is not valid"
InvalidAuthMethodStatus int = http.StatusBadRequest
EnrollmentExpiredCode string = "ENROLLMENT_EXPIRED"
EnrollmentExpiredMessage string = "The window for this enrollment has expired"
EnrollmentExpiredStatus int = http.StatusBadRequest
CouldNotProcessCsrCode string = "COULD_NOT_PROCESS_CSR"
CouldNotProcessCsrMessage string = "The supplied csr could not be processed"
CouldNotProcessCsrStatus int = http.StatusBadRequest
EnrollmentCaNoLongValidCode string = "ENROLLMENT_CA_NOT_VALID"
EnrollmentCaNoLongValidMessage string = "The CA tied to the supplied token is no longer valid"
EnrollmentCaNoLongValidStatus int = http.StatusBadRequest
EnrollmentNoValidCasCode string = "ENROLLMENT_NO_VALID_CAS"
EnrollmentNoValidCasMessage string = "No CAs are valid for this request"
EnrollmentNoValidCasStatus int = http.StatusBadRequest
InvalidEnrollmentTokenCode string = "INVALID_ENROLLMENT_TOKEN"
InvalidEnrollmentTokenMessage string = "The supplied token is not valid"
InvalidEnrollmentTokenStatus int = http.StatusBadRequest
InvalidEnrollMethodCode string = "INVALID_ENROLL_METHOD"
InvalidEnrollMethodMessage string = "The supplied enrollment method is not valid"
InvalidEnrollMethodStatus int = http.StatusBadRequest
InvalidFilterCode string = "INVALID_FILTER"
InvalidFilterMessage string = "The filter query supplied is invalid"
InvalidFilterStatus int = http.StatusBadRequest
InvalidPaginationCode string = "INVALID_PAGINATION"
InvalidPaginationMessage string = "The pagination properties provided are invalid"
InvalidPaginationStatus int = http.StatusBadRequest
NoEdgeRoutersAvailableCode string = "NO_EDGE_ROUTERS_AVAILABLE"
NoEdgeRoutersAvailableMessage string = "No edge routers are assigned and online to handle the requested connection"
NoEdgeRoutersAvailableStatus int = http.StatusBadRequest
InvalidSortCode string = "INVALID_SORT_IDENTIFIER"
InvalidSortMessage string = "The sort order supplied is invalid"
InvalidSortStatus int = http.StatusBadRequest
CouldNotValidateCode string = "COULD_NOT_VALIDATE"
CouldNotValidateMessage string = "The supplied request contains an invalid document"
CouldNotValidateStatus int = http.StatusBadRequest
UnauthorizedCode string = "UNAUTHORIZED"
UnauthorizedMessage string = "The request could not be completed. The session is not authorized or the credentials are invalid"
UnauthorizedStatus int = http.StatusUnauthorized
CouldNotDecodeProxiedCertCode string = "COULD_NOT_PARSE_PROXY_CERT"
CouldNotDecodeProxiedCertMessage string = "could not decode proxy client cert"
CouldNotDecodeProxiedCertStatus int = http.StatusInternalServerError
CouldNotParseX509FromDerCode string = "COULD_NOT_PARSE_x509_FROM_DER"
CouldNotParseX509FromDerMessage string = "could not parse x509 from DER"
CouldNotParseX509FromDerStatus int = http.StatusBadRequest
CertFailedValidationCode string = "CERT_FAILED_VALIDATION"
CertFailedValidationMessage string = "certificate failed to validate against CA"
CertFailedValidationStatus int = http.StatusBadRequest
CertInUseCode string = "CERT_IN_USE"
CertInUseMessage string = "The certificate supplied is already associated with another identity"
CertInUseStatus int = http.StatusConflict
CaAlreadyVerifiedCode string = "CA_ALREADY_VERIFIED"
CaAlreadyVerifiedMessage string = "CA has already been verified"
CaAlreadyVerifiedStatus int = http.StatusConflict
ExpectedPemBlockCertificateCode = "EXPECTED_PEM_CERTIFICATE"
ExpectedPemBlockCertificateMessage string = "expected PEM block type 'CERTIFICATE'"
ExpectedPemBlockCertificateStatus int = http.StatusBadRequest
CouldNotParseDerBlockCode string = "COULD_NOT_PARSE_DER_BLOCK"
CouldNotParseDerBlockMessage string = "The certificate's DER block could not be parsed"
CouldNotParseDerBlockStatus int = http.StatusBadRequest
CouldNotParsePemCode string = "COULD_NOT_PARSE_PEM_BLOCK"
CouldNotParsePemMessage string = "The certificate's PEM block could not be parsed"
CouldNotParsePemStatus int = http.StatusBadRequest
InvalidCommonNameCode string = "INVALID_COMMON_NAME"
InvalidCommonNameMessage string = "The common name of the supplied certificate is invalid"
InvalidCommonNameStatus int = http.StatusBadRequest
FailedCertificateValidationCode string = "CERTIFICATE_FAILED_VALIDATION"
FailedCertificateValidationMessage string = "The supplied certificate failed to validate against the CA's certificate chain"
FailedCertificateValidationStatus int = http.StatusBadRequest
CertificateIsNotCaCode string = "CERTIFICATE_IS_NOT_CA"
CertificateIsNotCaMessage string = "Leading certificate is not a CA"
CertificateIsNotCaStatus int = http.StatusBadRequest
)
+31
View File
@@ -0,0 +1,31 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 apierror
import "fmt"
type ValidationError struct {
Field string `json:"field"`
Type string `json:"type"`
Value interface{} `json:"value"`
Message string `json:"message"`
Details map[string]interface{} `json:"details"`
}
func (e ValidationError) Error() string {
return fmt.Sprintf("%s is invalid: %s", e.Field, e.Message)
}
+452
View File
@@ -0,0 +1,452 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 config
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
"bytes"
"crypto/sha1"
"encoding/pem"
"errors"
"fmt"
"github.com/gobuffalo/packr"
"github.com/michaelquigley/pfxlog"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
"time"
)
const (
sessionTimeoutDefault = 10
sessionTimeoutMin = 1
enrollmentDurationMin = 5
enrollmentDurationDefault = 1440
)
type Db struct {
ConnectionUrl string `yaml:"connectionUrl"`
DbName string `yaml:"dbName"`
}
type Persistence struct {
boxes []*packr.Box
Postgres *Db `yaml:"postgres"`
}
func (p *Persistence) AddMigrationBox(b *packr.Box) {
p.boxes = append(p.boxes, b)
}
func (p *Persistence) GetBoxes() []*packr.Box {
return p.boxes
}
type Enrollment struct {
SigningCert identity.Identity
SigningCertConfig identity.IdentityConfig
SigningCertCaPem []byte
EdgeIdentity EnrollmentOption
EdgeRouter EnrollmentOption
}
type EnrollmentOption struct {
DurationMinutes time.Duration
}
type Api struct {
SessionTimeoutSeconds time.Duration
Listener string
Advertise string
Identity identity.Identity
IdentityConfig identity.IdentityConfig
IdentityCaPem []byte
}
type Config struct {
RootIdentityConfig identity.IdentityConfig
RootIdentity identity.Identity
RootIdentityCaPem []byte
Enabled bool
Api Api
Enrollment Enrollment
Persistence Persistence
caPems [][]byte
caPemsBuf []byte
caPemsOnce sync.Once
}
func (c *Config) SessionTimeoutDuration() time.Duration {
return c.Api.SessionTimeoutSeconds
}
func toHex(data []byte) string {
var buf bytes.Buffer
for i, b := range data {
if i > 0 {
_, _ = fmt.Fprintf(&buf, ":")
}
_, _ = fmt.Fprintf(&buf, "%02x", b)
}
return strings.ToUpper(buf.String())
}
func (c *Config) CaPems() []byte {
c.caPemsOnce.Do(func() {
buf := bytes.Buffer{}
//dedupe chains
pemMap := map[string][]byte{}
for _, caChain := range c.caPems {
rest := caChain
for len(rest) != 0 {
var block *pem.Block
block, rest = pem.Decode(rest)
if block != nil {
hash := sha1.Sum(block.Bytes)
fingerprint := toHex(hash[:])
pemMap[fingerprint] = pem.EncodeToMemory(block)
}
}
}
i := 0
for _, pemBytes := range pemMap {
if i != 0 {
buf.Write([]byte("\n"))
}
buf.Write(pemBytes)
i++
}
c.caPemsBuf = buf.Bytes()
})
return c.caPemsBuf
}
func (c *Config) loadRootIdentity(fabricConfigMap map[interface{}]interface{}) error {
var fabricIdentitySubMap map[interface{}]interface{}
if value, found := fabricConfigMap["identity"]; found {
fabricIdentitySubMap = value.(map[interface{}]interface{})
} else {
return errors.New("required configuration value [identity] missing")
}
if value, found := fabricIdentitySubMap["cert"]; found {
c.RootIdentityConfig.Cert = value.(string)
} else {
return fmt.Errorf("required configuration value [identity.cert] is missing")
}
if value, found := fabricIdentitySubMap["server_cert"]; found {
c.RootIdentityConfig.ServerCert = value.(string)
} else {
return fmt.Errorf("required configuration value [identity.server_cert] is missing")
}
if value, found := fabricIdentitySubMap["key"]; found {
c.RootIdentityConfig.Key = value.(string)
} else {
return fmt.Errorf("required configuration value [identity.key] is missing")
}
if value, found := fabricIdentitySubMap["server_key"]; found {
c.RootIdentityConfig.ServerKey = value.(string)
} //allow "key" to be the default, this isn't an error
if value, found := fabricIdentitySubMap["ca"]; found {
c.RootIdentityConfig.CA = value.(string)
}
var err error
if c.RootIdentityCaPem, err = ioutil.ReadFile(c.RootIdentityConfig.CA); err != nil {
return fmt.Errorf("could not read file CA file from [identity.ca]")
}
c.caPems = append(c.caPems, c.RootIdentityCaPem)
c.RootIdentity, err = identity.LoadIdentity(c.RootIdentityConfig)
return err
}
func (c *Config) loadApiSection(edgeConfigMap map[interface{}]interface{}) error {
c.Api = Api{}
var err error
if value, found := edgeConfigMap["api"]; found {
submap := value.(map[interface{}]interface{})
if value, found := submap["listener"]; found {
c.Api.Listener = value.(string)
} else {
return errors.New("required configuration value [edge.api.listener] missing")
}
if value, found := submap["advertise"]; found {
c.Api.Advertise = value.(string)
} else {
return errors.New("required configuration value [edge.api.advertise] missing")
}
var intValue = 0
if value, found := submap["sessionTimeoutMinutes"]; found {
intValue = value.(int)
}
if intValue < sessionTimeoutMin {
intValue = sessionTimeoutDefault
pfxlog.Logger().Warn("[edge.api.sessionTimeout] defaulted to " + strconv.Itoa(intValue))
}
c.Api.SessionTimeoutSeconds = time.Duration(intValue) * time.Minute
var apiIdentitySubMap map[interface{}]interface{}
if value, found = submap["identity"]; found {
apiIdentitySubMap = value.(map[interface{}]interface{})
}
if err = c.loadIApiIdentity(apiIdentitySubMap); err != nil {
return fmt.Errorf("error loading Edge API Identity: %s", err)
}
} else {
return errors.New("required configuration section [edge.api] missing")
}
return nil
}
func (c *Config) loadIApiIdentity(apiIdentitySubMap map[interface{}]interface{}) error {
//default to root identity value
c.Api.IdentityConfig = identity.IdentityConfig{
Key: c.RootIdentityConfig.Key,
Cert: c.RootIdentityConfig.Cert,
ServerCert: c.RootIdentityConfig.ServerCert,
ServerKey: c.RootIdentityConfig.ServerKey,
CA: c.RootIdentityConfig.CA,
}
if apiIdentitySubMap != nil {
if value, found := apiIdentitySubMap["server_cert"]; found {
c.Api.IdentityConfig.ServerCert = value.(string)
} else {
return fmt.Errorf("configuration value [edge.api.identity.server_cert] is required if [edge.api.identity] is specified")
}
if value, found := apiIdentitySubMap["server_key"]; found {
c.Api.IdentityConfig.ServerKey = value.(string)
} else {
return fmt.Errorf("configuration value [edge.api.identity.server_key] is required if [edge.api.identity] is specified")
}
if value, found := apiIdentitySubMap["ca"]; found {
c.Api.IdentityConfig.CA = value.(string)
var err error
if c.Api.IdentityCaPem, err = ioutil.ReadFile(c.Api.IdentityConfig.CA); err != nil {
return fmt.Errorf("could not read file CA file from [edge.api.identity.ca]")
}
c.caPems = append(c.caPems, c.Api.IdentityCaPem)
}
}
var err error
c.Api.Identity, err = identity.LoadIdentity(c.Api.IdentityConfig)
return err
}
func (c *Config) loadEnrollmentSection(edgeConfigMap map[interface{}]interface{}) error {
c.Enrollment = Enrollment{}
var err error
if value, found := edgeConfigMap["enrollment"]; found {
submap := value.(map[interface{}]interface{})
if value, found := submap["signingCert"]; found {
submap := value.(map[interface{}]interface{})
c.Enrollment.SigningCertConfig = identity.IdentityConfig{}
if value, found := submap["cert"]; found {
c.Enrollment.SigningCertConfig.Cert = value.(string)
certPem, err := ioutil.ReadFile(c.Enrollment.SigningCertConfig.Cert)
if err != nil {
pfxlog.Logger().WithError(err).Panic("unable to read [edge.enrollment.cert]")
}
//The signer is a valid trust anchor
c.caPems = append(c.caPems, certPem)
} else {
return fmt.Errorf("required configuration value [edge.enrollment.cert] is missing")
}
if value, found := submap["key"]; found {
c.Enrollment.SigningCertConfig.Key = value.(string)
} else {
return fmt.Errorf("required configuration value [edge.enrollment.key] is missing")
}
if value, found := submap["ca"]; found {
c.Enrollment.SigningCertConfig.CA = value.(string)
if c.Enrollment.SigningCertCaPem, err = ioutil.ReadFile(c.Enrollment.SigningCertConfig.CA); err != nil {
return fmt.Errorf("could not read file CA file from [edge.enrollment.signingCert.ca]")
}
c.caPems = append(c.caPems, c.Enrollment.SigningCertCaPem)
} //not an error if the signing cert's CA is already represented in the root [identity.ca]
if c.Enrollment.SigningCert, err = identity.LoadIdentity(c.Enrollment.SigningCertConfig); err != nil {
return fmt.Errorf("error loading [edge.enrollment.signingCert]: %s", err)
}
} else {
return errors.New("required configuration section [edge.enrollment.signingCert] missing")
}
if value, found := submap["edgeIdentity"]; found {
submap := value.(map[interface{}]interface{})
var edgeIdentityDurationInt = 0
if value, found := submap["durationMinutes"]; found {
edgeIdentityDurationInt = value.(int)
}
if edgeIdentityDurationInt < enrollmentDurationMin {
edgeIdentityDurationInt = enrollmentDurationDefault
}
c.Enrollment.EdgeIdentity = EnrollmentOption{DurationMinutes: time.Duration(edgeIdentityDurationInt) * time.Minute}
} else {
return errors.New("required configuration section [edge.enrollment.edgeIdentity] missing")
}
if value, found := submap["edgeRouter"]; found {
submap := value.(map[interface{}]interface{})
var edgeRouterDurationInt = 0
if value, found := submap["durationMinutes"]; found {
edgeRouterDurationInt = value.(int)
}
if edgeRouterDurationInt < enrollmentDurationMin {
edgeRouterDurationInt = enrollmentDurationDefault
}
c.Enrollment.EdgeRouter = EnrollmentOption{DurationMinutes: time.Duration(edgeRouterDurationInt) * time.Minute}
} else {
return errors.New("required configuration section [edge.enrollment.edgeRouter] missing")
}
} else {
return errors.New("required configuration section [edge.enrollment] missing")
}
return nil
}
func (c *Config) loadPersistenceSection(edgeConfigMap map[interface{}]interface{}) error {
c.Persistence = Persistence{Postgres: &Db{}}
if value, found := edgeConfigMap["persistence"]; found {
submap := value.(map[interface{}]interface{})
if value, found := submap["connectionUrl"]; found {
c.Persistence.Postgres.ConnectionUrl = value.(string)
} else {
return errors.New("required configuration value [edge.persistence.connectionUrl] missing")
}
if value, found := submap["dbName"]; found {
c.Persistence.Postgres.DbName = value.(string)
} else {
return errors.New("required configuration value [edge.persistence.dbName] missing")
}
} //no longer required
return nil
}
func LoadFromMap(cfgmap map[interface{}]interface{}) (*Config, error) {
edgeConfig := &Config{
Enabled: false,
}
var edgeConfigMap map[interface{}]interface{} = nil
if val, ok := cfgmap["edge"]; ok && val != nil {
if edgeConfigMap, ok = val.(map[interface{}]interface{}); !ok {
return nil, fmt.Errorf("expected map as edge configuration")
}
} else {
return edgeConfig, nil
}
edgeConfig.Enabled = cfgmap != nil
if !edgeConfig.Enabled {
return edgeConfig, nil
}
var err error
if err = edgeConfig.loadRootIdentity(cfgmap); err != nil {
return nil, err
}
if err = edgeConfig.loadApiSection(edgeConfigMap); err != nil {
return nil, err
}
if err = edgeConfig.loadEnrollmentSection(edgeConfigMap); err != nil {
return nil, err
}
if err = edgeConfig.loadPersistenceSection(edgeConfigMap); err != nil {
return nil, err
}
return edgeConfig, nil
}
func LoadFromFile(path string) (*Config, error) {
c := map[interface{}]interface{}{}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
d := yaml.NewDecoder(f)
err = d.Decode(c)
if err != nil {
return nil, err
}
return LoadFromMap(c)
}
+23
View File
@@ -0,0 +1,23 @@
This project uses pakr (https://github.com/gobuffalo/packr) to embed the JSON
schema file within the resulting binaries. Working with these files requires
the installation of the pakr utility command.
- go get -u github.com/gobuffalo/packr/packr
The entire, edge/embedded directory structure is embedded into the controller
binary by default.
To ensure that no extra steps are needed for a consumer of this source code,
"Building a Binary (the hard way)" as documented in the packr documentation
is used. This means that the author of any files in the /embedded directory
must run the packr utility command after altering/adding/removing files.
To run the packr utility, navigate to the /ziti project root and execute:
> bin/packr -z
It should scan the source and repack any files. "*-packr.go" files will be
generated. These files SHOULD be checked into source (against the packr
recommendations). This alleviates consumers from having to install and run
the packr utility.
@@ -0,0 +1,120 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/definitions.all.json",
"definitions": {
"tags": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^.{1,50}$": {
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"minLength": 1,
"maxLength": 2048
},
{
"type": "number",
"minLength": 1,
"maxLength": 2048
},
{
"type": "boolean"
}
]
}
}
},
"tags-null": {
"type": ["object", "null"],
"additionalProperties": false,
"patternProperties": {
"^.{1,50}$": {
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"minLength": 1,
"maxLength": 2048
},
{
"type": "number",
"minLength": 1,
"maxLength": 2048
},
{
"type": "boolean"
}
]
}
}
},
"name": {
"type": "string",
"minLength": 2,
"maxLength": 256
},
"name-null": {
"type": ["string", "null"],
"minLength": 2,
"maxLength": 256
},
"id": {
"type": "string",
"format": "uuid",
"minLength": 1,
"maxLength": 256
},
"id-null": {
"type": ["string", "null"],
"format": "uuid",
"minLength": 1,
"maxLength": 256
},
"id-str": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"id-str-null": {
"type": ["string", "null"],
"minLength": 1,
"maxLength": 256
},
"arrayOfIds": {
"type": "array",
"default": [],
"items": {
"$ref": "#/definitions/id"
}
},
"arrayOfIds-null": {
"type": ["array", "null"],
"default": [],
"items": {
"$ref": "#/definitions/id"
}
},
"arrayOfIds-str": {
"type": "array",
"default": [],
"items": {
"$ref": "#/definitions/id-str"
}
},
"password": {
"type": "string",
"minLength": 6,
"maxLength": 100
},
"username": {
"type": "string",
"minLength": 4,
"maxLength": 100
}
}
}
@@ -0,0 +1,14 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/app-wans.patch.json",
"type": "object",
"additionalProperties": false,
"required": [],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name-null"
},
"tags": {
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,26 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/app-wans.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"identities",
"services"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"identities": {
"default": [],
"$ref": "definitions.all.json#/definitions/arrayOfIds"
},
"services": {
"default": [],
"$ref": "definitions.all.json#/definitions/arrayOfIds"
},
"tags": {
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,23 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/app-wans.put.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"tags"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"identities": {
"$ref": "definitions.all.json#/definitions/arrayOfIds"
},
"services": {
"$ref": "definitions.all.json#/definitions/arrayOfIds"
},
"tags": {
"$ref": "definitions.all.json#/definitions/tags"
}
}
}
@@ -0,0 +1,13 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/association.patch.json",
"type": "object",
"additionalProperties": false,
"required": [
"ids"
],
"properties": {
"ids": {
"$ref": "definitions.all.json#/definitions/arrayOfIds"
}
}
}
@@ -0,0 +1,13 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/association.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"ids"
],
"properties": {
"ids": {
"$ref": "definitions.all.json#/definitions/arrayOfIds"
}
}
}
@@ -0,0 +1,13 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/association.put.json",
"type": "object",
"additionalProperties": false,
"required": [
"ids"
],
"properties": {
"ids": {
"$ref": "definitions.all.json#/definitions/arrayOfIds"
}
}
}
@@ -0,0 +1,31 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/cas.patch.json",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name-null"
},
"isAutoCaEnrollmentEnabled": {
"type": [
"boolean",
"null"
]
},
"isOttCaEnrollmentEnabled": {
"type": [
"boolean",
"null"
]
},
"isAuthEnabled": {
"type": [
"boolean",
"null"
]
},
"tags": {
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,34 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/cas.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"certPem"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"certPem": {
"type": "string",
"minLength": 1,
"maxLength": 20480
},
"isAutoCaEnrollmentEnabled": {
"type": "boolean",
"default": false
},
"isOttCaEnrollmentEnabled": {
"type": "boolean",
"default": false
},
"isAuthEnabled": {
"type": "boolean",
"default": false
},
"tags": {
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,32 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/cas.put.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"tags",
"isAutoCaEnrollmentEnabled",
"isOttCaEnrollmentEnabled",
"isAuthEnabled"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"isAutoCaEnrollmentEnabled": {
"type": "boolean",
"default": false
},
"isOttCaEnrollmentEnabled": {
"type": "boolean",
"default": false
},
"isAuthEnabled": {
"type": "boolean",
"default": false
},
"tags": {
"$ref": "definitions.all.json#/definitions/tags"
}
}
}
@@ -0,0 +1,16 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/clusters.patch.json",
"type": "object",
"additionalProperties": false,
"required": [
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name-null"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,17 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/clusters.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,18 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/clusters.put.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"tags"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags"
}
}
}
@@ -0,0 +1,4 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/devices.post.json",
"type": "object"
}
@@ -0,0 +1,19 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/edge-routers.patch.json",
"type": "object",
"additionalProperties": false,
"required": [
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name-null"
},
"clusterId": {
"$ref": "definitions.all.json#/definitions/id-null"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,21 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/edge-routers.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"clusterId"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"clusterId": {
"$ref": "definitions.all.json#/definitions/id"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,22 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/edge-routers.put.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"clusterId",
"tags"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"clusterId": {
"$ref": "definitions.all.json#/definitions/id"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags"
}
}
}
@@ -0,0 +1,21 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/enroll-er.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"serverCertCsr",
"certCsr"
],
"properties": {
"certCsr": {
"type": "string",
"minLength": 1,
"maxLength": 5120
},
"serverCertCsr": {
"type": "string",
"minLength": 1,
"maxLength": 5120
}
}
}
@@ -0,0 +1,13 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/enroll-updb.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"password"
],
"properties": {
"password": {
"$ref": "definitions.all.json#/definitions/password"
}
}
}
@@ -0,0 +1,12 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/enroll.post.json",
"type": "object",
"additionalProperties": false,
"required": [
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name-null"
}
}
}
@@ -0,0 +1,45 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/identities.patch.json",
"type": "object",
"additionalProperties": false,
"required": [
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name-null"
},
"type": {
"type": [
"string",
"null"
],
"enum": [
"User",
"Device",
"Service"
]
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
},
"isAdmin": {
"type": [
"boolean",
"null"
],
"default": false
},
"permissions": {
"type": [
"array",
"null"
],
"items": {
"$ref": "definitions.all.json#/definitions/name"
},
"default": []
}
}
}
@@ -0,0 +1,57 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/identities.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"type",
"permissions",
"isAdmin"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"type": {
"type": "string",
"enum": [
"User",
"Device",
"Service"
]
},
"enrollment": {
"type": [
"object",
"null"
],
"properties": {
"ott": {
"type": "boolean"
},
"ottca": {
"$ref": "definitions.all.json#/definitions/id"
},
"updb": {
"$ref": "definitions.all.json#/definitions/username"
}
}
},
"isAdmin": {
"type": "boolean",
"default": false
},
"permissions": {
"type": "array",
"items": {
"$ref": "definitions.all.json#/definitions/name"
},
"default": []
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,41 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/identities.put.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"type",
"tags",
"isAdmin",
"permissions"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"type": {
"type": "string",
"enum": [
"User",
"Device",
"Service"
]
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags"
},
"isAdmin": {
"type": "boolean",
"default": false
},
"permissions": {
"type": "array",
"items": {
"$ref": "definitions.all.json#/definitions/name"
},
"default": []
}
}
}
@@ -0,0 +1,16 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/network-sessions.post.json",
"type": "object",
"properties": {
"serviceId": {
"type": "string"
},
"hosting": {
"type": "boolean",
"default": false
}
},
"required": [
"serviceId"
]
}
@@ -0,0 +1,4 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/policies.post.json",
"type": "object"
}
@@ -0,0 +1,51 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/services.patch.json",
"type": "object",
"additionalProperties": false,
"required": [
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name-null"
},
"dns": {
"type": [
"object",
"null"
],
"additionalProperties": false,
"required": [
],
"properties": {
"hostname": {
"type": [
"string",
"null"
]
},
"port": {
"type": [
"string",
"null"
]
}
}
},
"egressRouter": {
"type": [
"string",
"null"
]
},
"endpointAddress": {
"type": [
"string",
"null"
]
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,48 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/services.post.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"dns",
"egressRouter",
"endpointAddress"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"hostIds": {
"$ref": "definitions.all.json#/definitions/arrayOfIds-null"
},
"dns": {
"type": "object",
"additionalProperties": false,
"required": [
"hostname",
"port"
],
"properties": {
"hostname": {
"type": "string"
},
"port": {
"type": "integer"
}
}
},
"egressRouter": {
"type": "string"
},
"endpointAddress": {
"type": "string"
},
"clusters": {
"$ref": "definitions.all.json#/definitions/arrayOfIds"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,42 @@
{
"$id": "http://ziti-edge.netfoundry.io/schemas/services.put.json",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"dns",
"endpointAddress",
"egressRouter"
],
"properties": {
"name": {
"$ref": "definitions.all.json#/definitions/name"
},
"dns": {
"type": "object",
"additionalProperties": false,
"required": [
"hostname",
"port"
],
"properties": {
"hostname": {
"type": "string"
},
"port": {
"type": "integer"
}
}
},
"egressRouter": {
"type": "string"
},
"endpointAddress": {
"type": "string"
},
"tags": {
"default": {},
"$ref": "definitions.all.json#/definitions/tags-null"
}
}
}
@@ -0,0 +1,12 @@
DO $$
BEGIN
IF EXISTS(
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'ziti_edge'
)
THEN
EXECUTE 'DROP SCHEMA ziti_edge CASCADE';
END IF;
END
$$;
@@ -0,0 +1,14 @@
DO $$
BEGIN
IF EXISTS(
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'ziti_edge'
)
THEN
EXECUTE 'DROP SCHEMA ziti_edge CASCADE';
END IF;
END
$$;
CREATE SCHEMA ziti_edge;
@@ -0,0 +1,248 @@
CREATE TABLE ziti_edge.geo_regions (
id UUID PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
CREATE TABLE ziti_edge.cas
(
id uuid PRIMARY KEY NOT NULL,
name varchar(250) NOT NULL,
fingerprint varchar(512) NOT NULL,
cert_pem text NOT NULL,
is_verified boolean NOT NULL,
verification_token varchar(512) NOT NULL,
is_auto_ca_enrollment_enabled boolean NOT NULL,
is_ott_ca_enrollment_enabled boolean NOT NULL,
is_auth_enabled boolean NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
CREATE UNIQUE INDEX cas_fingerprint__uindex
ON ziti_edge.cas (fingerprint);
CREATE TABLE ziti_edge.identity_types
(
id UUID PRIMARY KEY NOT NULL,
name varchar(100) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
CREATE UNIQUE INDEX identity_types_id__uindex
ON ziti_edge.identity_types (id);
CREATE TABLE ziti_edge.identities
(
id UUID PRIMARY KEY NOT NULL,
name varchar(250) NOT NULL,
identity_type_id UUID NOT NULL,
is_default_admin boolean DEFAULT false NOT NULL,
is_admin boolean DEFAULT false NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
CREATE UNIQUE INDEX identities_id__uindex
ON ziti_edge.identities (id);
ALTER TABLE ziti_edge.identities
ADD CONSTRAINT identities_identity_types_id__fk FOREIGN KEY (identity_type_id) REFERENCES ziti_edge.identity_types (id);
CREATE TABLE ziti_edge.sessions (
id UUID PRIMARY KEY NOT NULL,
token UUID NOT NULL,
identity_id UUID NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
ALTER TABLE ziti_edge.sessions
ADD CONSTRAINT sessions_identity_id__fk FOREIGN KEY (identity_id) REFERENCES ziti_edge.identities (id);
CREATE UNIQUE INDEX sessions_token__uindex
ON ziti_edge.sessions (token);
CREATE TABLE ziti_edge.clusters (
id UUID PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
CREATE TABLE ziti_edge.gateways (
id UUID PRIMARY KEY NOT NULL,
cluster_id uuid NOT NULL,
name varchar(255) NOT NULL,
fingerprint varchar(1024) NULL,
cert_pem text NULL,
is_verified boolean DEFAULT false NOT NULL,
is_online boolean DEFAULT false NOT NULL,
geo_region_id UUID NULL,
enrollment_token UUID NULL,
enrollment_jwt text NULL,
enrollment_created_at timestamp with time zone NULL,
enrollment_expires_at timestamp with time zone NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
ALTER TABLE ziti_edge.gateways
ADD CONSTRAINT gateways_cluster_id__fk FOREIGN KEY (cluster_id) REFERENCES ziti_edge.clusters (id);
ALTER TABLE ziti_edge.gateways
ADD CONSTRAINT gateways_geo_region_id__fk FOREIGN KEY (geo_region_id) REFERENCES ziti_edge.geo_regions (id);
CREATE TABLE ziti_edge.protocols (
id varchar(20) PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
CREATE TABLE ziti_edge.fabric_types (
id varchar(20) PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
CREATE TABLE ziti_edge.fabrics (
id UUID PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
fabric_type_id varchar(20) NOT NULL,
config json NOT NULL DEFAULT '{}' :: jsonb,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
ALTER TABLE ziti_edge.fabrics
ADD CONSTRAINT fabrics_fabric_type_id__fk FOREIGN KEY (fabric_type_id) REFERENCES ziti_edge.fabric_types (id);
CREATE TABLE ziti_edge.services (
id UUID PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
dns_hostname varchar(2048) NOT NULL,
dns_port integer NOT NULL,
fabric_id UUID NOT NULL,
fabric_config json NOT NULL DEFAULT '{}' :: jsonb,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
ALTER TABLE ziti_edge.services
ADD CONSTRAINT services_fabric_id__fk FOREIGN KEY (fabric_id) REFERENCES ziti_edge.fabrics (id);
-- Table is deprecated as of 2019-06-10
CREATE TABLE ziti_edge.service_protocols (
service_id UUID NOT NULL,
protocol_id varchar(20) NOT NULL
);
ALTER TABLE ziti_edge.service_protocols
ADD CONSTRAINT service_protocols__pk PRIMARY KEY (service_id, protocol_id);
ALTER TABLE ziti_edge.service_protocols
ADD CONSTRAINT service_protocols_service_id__fk FOREIGN KEY (service_id) REFERENCES ziti_edge.services (id);
ALTER TABLE ziti_edge.service_protocols
ADD CONSTRAINT service_protocols_protocol_id__fk FOREIGN KEY (protocol_id) REFERENCES ziti_edge.protocols (id);
CREATE TABLE ziti_edge.service_clusters (
service_id UUID NOT NULL,
cluster_id UUID NOT NULL
);
ALTER TABLE ziti_edge.service_clusters
ADD CONSTRAINT service_clusters__pk PRIMARY KEY (service_id, cluster_id);
ALTER TABLE ziti_edge.service_clusters
ADD CONSTRAINT service_clusters_service_id__fk FOREIGN KEY (service_id) REFERENCES ziti_edge.services (id);
ALTER TABLE ziti_edge.service_clusters
ADD CONSTRAINT service_clusters_cluster_id__fk FOREIGN KEY (cluster_id) REFERENCES ziti_edge.clusters (id);
CREATE TABLE ziti_edge.network_sessions (
id UUID PRIMARY KEY NOT NULL,
token UUID NOT NULL,
session_id UUID NOT NULL,
service_id UUID NOT NULL,
cluster_id UUID NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
ALTER TABLE ziti_edge.network_sessions
ADD CONSTRAINT network_sessions_session_id__fk FOREIGN KEY (session_id) REFERENCES ziti_edge.sessions (id);
ALTER TABLE ziti_edge.network_sessions
ADD CONSTRAINT network_sessions_service_id__fk FOREIGN KEY (service_id) REFERENCES ziti_edge.services (id);
ALTER TABLE ziti_edge.network_sessions
ADD CONSTRAINT network_sessions_cluster_id__fk FOREIGN KEY (cluster_id) REFERENCES ziti_edge.clusters (id);
CREATE UNIQUE INDEX network_sessions_token__uindex
ON ziti_edge.network_sessions (token);
CREATE TABLE ziti_edge.app_wans
(
id UUID PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb NOT NULL
);
CREATE TABLE ziti_edge.app_wan_identities
(
app_wan_id UUID NOT NULL,
identity_id UUID NOT NULL
);
ALTER TABLE ziti_edge.app_wan_identities
ADD CONSTRAINT app_wan_identities__pk PRIMARY KEY (app_wan_id, identity_id);
ALTER TABLE ziti_edge.app_wan_identities
ADD CONSTRAINT app_wan_identities_identity_id__fk FOREIGN KEY (identity_id) REFERENCES ziti_edge.identities (id);
ALTER TABLE ziti_edge.app_wan_identities
ADD CONSTRAINT app_wan_identities_app_wan_id__fk FOREIGN KEY (app_wan_id) REFERENCES ziti_edge.app_wans (id);
CREATE TABLE ziti_edge.app_wan_services
(
app_wan_id UUID NOT NULL,
service_id UUID NOT NULL
);
ALTER TABLE ziti_edge.app_wan_services
ADD CONSTRAINT app_wan_services__pk PRIMARY KEY (app_wan_id, service_id);
ALTER TABLE ziti_edge.app_wan_services
ADD CONSTRAINT app_wan_services_services_id__fk FOREIGN KEY (service_id) REFERENCES ziti_edge.services (id);
ALTER TABLE ziti_edge.app_wan_services
ADD CONSTRAINT app_wan_services_app_wan_id__fk FOREIGN KEY (app_wan_id) REFERENCES ziti_edge.app_wans (id);
@@ -0,0 +1,81 @@
INSERT INTO "ziti_edge"."identity_types" ("id", "name", "created_at", "updated_at")
VALUES ('577104f2-1e3a-4947-a927-7383baefbc9a', 'User', DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."identity_types" ("id", "name", "created_at", "updated_at")
VALUES ('5b53fb49-51b1-4a87-a4e4-edda9716a970', 'Device', DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."identity_types" ("id", "name", "created_at", "updated_at")
VALUES ('c4d66f9d-fe18-4143-85d3-74329c54282b', 'Service', DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."protocols" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('tls', 'tls', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."protocols" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('quic', 'quic', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."protocols" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('tcp', 'tcp', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."fabric_types" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('zt-passthrough', 'Passthrough', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."fabric_types" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('zt-native', 'Ziti Native', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."fabrics" ("id", "name", "fabric_type_id", "config", "created_at", "updated_at", "tags")
VALUES ('ffb41b14-d539-4db0-8651-ec1d62408023', 'Passthrough', 'zt-passthrough', DEFAULT, DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('a0e2c29f-9922-4435-a8a7-5dbf7bd92377', 'Canada Central', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('ac469973-105c-4de1-9f31-fffc077487fb', 'US West', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('2360479d-cc08-4224-bd56-43baa672af30', 'Japan', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('edd0680f-3ab4-49e6-9db5-c68258ba480d', 'Australia', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('521a3db9-8140-4854-a782-61cb5d3fe043', 'South America', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('8342acad-4e49-4098-84de-829feb55d350', 'Korea', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('b5562b15-ffeb-4910-bf14-a03067f9ca2e', 'US Midwest', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('6efe28a5-744e-464d-b147-4072efb769f0', 'Canada West', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('10c6f648-92b7-49e2-be96-f62357ea572f', 'Europe West', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('72946251-1fc7-4b3b-8568-c59d4723e704', 'Africa', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('70438d63-97b3-48b2-aeb5-b066a9526456', 'Europe East', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('e339d699-7f51-4e9c-a2ca-81720e07f196', 'US South', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('00586703-6748-4c78-890d-efa216f21ef3', 'Canada East', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('63f7200b-7794-4a68-92aa-a36ed338ecba', 'Asia', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('f91ecca3-6f82-4c7b-8191-ea0036ce7b5a', 'US East', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('41929e78-6674-4708-89d2-9b934ea96822', 'Middle East', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."geo_regions" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('5d0042bb-6fd5-4959-90a7-6bca70e23f76', 'US Central', DEFAULT, DEFAULT, DEFAULT);
@@ -0,0 +1 @@
DROP TABLE ziti_edge.gateway_protocols
@@ -0,0 +1,19 @@
CREATE TABLE ziti_edge.gateway_protocols
(
id UUID PRIMARY KEY NOT NULL,
gateway_id UUID NOT NULL,
protocol_id varchar(20) NOT NULL,
ingress_url varchar(8192) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
ALTER TABLE ziti_edge.gateway_protocols
ADD CONSTRAINT gateway_protocols_gateway_id__fk FOREIGN KEY (gateway_id) REFERENCES ziti_edge.gateways (id);
ALTER TABLE ziti_edge.gateway_protocols
ADD CONSTRAINT gateway_protocols_protocol_id__fk FOREIGN KEY (protocol_id) REFERENCES ziti_edge.protocols (id);
ALTER TABLE ziti_edge.network_sessions DROP COLUMN cluster_id;
@@ -0,0 +1 @@
ALTER TABLE ziti_edge.gateways DROP COLUMN hostname;
@@ -0,0 +1,2 @@
ALTER TABLE ziti_edge.gateways
ADD hostname VARCHAR(512);
@@ -0,0 +1,15 @@
create table ziti_edge.enrollments
(
id uuid not null
constraint enrollments_pkey
primary key,
identity_id uuid not null
constraint enrollments_identity_id__fk
references ziti_edge.identities,
token varchar(100) not null,
method varchar(256) not null,
expires_at timestamp with time zone not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null,
tags json default '{}'::jsonb not null
);
@@ -0,0 +1 @@
drop table ziti_edge.authenticators;
@@ -0,0 +1,12 @@
create table ziti_edge.authenticators
(
id uuid not null
constraint authenticators_pkey primary key,
identity_id uuid not null
constraint authenticators_identity_id__fk references ziti_edge.identities,
method varchar(256) not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null,
tags json default '{}' :: jsonb not null
);
@@ -0,0 +1 @@
drop table ziti_edge.network_session_certs;
@@ -0,0 +1,16 @@
create table ziti_edge.network_session_certs
(
id uuid not null
constraint network_session_certs_pkey
primary key,
network_session_id uuid not null
constraint network_session_cert_network_sessions_id__fk
references ziti_edge.network_sessions,
cert text not null,
fingerprint varchar(512) not null,
valid_from timestamp with time zone default null,
valid_to timestamp with time zone default null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null,
tags json default '{}'::jsonb not null
);
@@ -0,0 +1 @@
drop table ziti_edge.event_logs;
@@ -0,0 +1,17 @@
create table ziti_edge.event_logs
(
id uuid not null
constraint event_logs_pkey primary key,
type varchar(256) not null,
actor_type varchar(256) null,
actor_id varchar(256) null,
entity_type varchar(256) null,
entity_id varchar(256) null,
formatted_message text not null,
format_string text not null,
format_data text null,
data jsonb null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null,
tags json default '{}' :: jsonb not null
);
@@ -0,0 +1 @@
drop TABLE ziti_edge.service_hosts;
@@ -0,0 +1,15 @@
CREATE TABLE ziti_edge.service_hosts (
service_id UUID NOT NULL,
identity_id UUID NOT NULL
);
ALTER TABLE ziti_edge.service_hosts
ADD CONSTRAINT service_hosts__pk PRIMARY KEY (service_id, identity_id);
ALTER TABLE ziti_edge.service_hosts
ADD CONSTRAINT service_hosts_service_id__fk FOREIGN KEY (service_id) REFERENCES ziti_edge.services (id);
ALTER TABLE ziti_edge.service_hosts
ADD CONSTRAINT service_hosts_protocol_id__fk FOREIGN KEY (identity_id) REFERENCES ziti_edge.identities (id);
ALTER TABLE ziti_edge.network_sessions ADD COLUMN IF NOT EXISTS hosting boolean DEFAULT false;
@@ -0,0 +1,20 @@
alter table ziti_edge.services
drop constraint services_fabric_id__fk;
alter table ziti_edge.services
drop column fabric_id;
alter table ziti_edge.services
drop column fabric_config;
alter table ziti_edge.services
add legacy_passthrough bool default false not null;
alter table ziti_edge.services
add endpoint_address varchar(2048);
alter table ziti_edge.services
add egress_router varchar(2048) not null default 'unknown';
drop table ziti_edge.fabrics;
drop table ziti_edge.fabric_types;
@@ -0,0 +1,2 @@
alter table ziti_edge.gateways add column is_online boolean not null default false;
@@ -0,0 +1 @@
alter table ziti_edge.gateways drop column is_online;
@@ -0,0 +1,18 @@
CREATE TABLE ziti_edge.gateway_protocols
(
id UUID PRIMARY KEY NOT NULL,
gateway_id UUID NOT NULL,
protocol_id varchar(20) NOT NULL,
ingress_url varchar(8192) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
ALTER TABLE ziti_edge.gateway_protocols
ADD CONSTRAINT gateway_protocols_gateway_id__fk FOREIGN KEY (gateway_id) REFERENCES ziti_edge.gateways (id);
ALTER TABLE ziti_edge.gateway_protocols
ADD CONSTRAINT gateway_protocols_protocol_id__fk FOREIGN KEY (protocol_id) REFERENCES ziti_edge.protocols (id);
alter table ziti_edge.gateways add column hostname varchar(512)
@@ -0,0 +1,3 @@
drop table ziti_edge.gateway_protocols;
alter table ziti_edge.gateways drop column hostname;
@@ -0,0 +1,14 @@
create table ziti_edge.service_protocols
(
service_id uuid not null
constraint service_protocols_service_id__fk
references ziti_edge.services,
protocol_id varchar(20) not null
constraint service_protocols_protocol_id__fk
references ziti_edge.protocols,
constraint service_protocols__pk
primary key (service_id, protocol_id)
);
alter table ziti_edge.service_protocols owner to postgres;
@@ -0,0 +1 @@
drop table ziti_edge.service_protocols;
@@ -0,0 +1,16 @@
CREATE TABLE ziti_edge.protocols (
id varchar(20) PRIMARY KEY NOT NULL,
name varchar(255) NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
tags json NOT NULL DEFAULT '{}' :: jsonb
);
INSERT INTO "ziti_edge"."protocols" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('tls', 'tls', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."protocols" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('quic', 'quic', DEFAULT, DEFAULT, DEFAULT);
INSERT INTO "ziti_edge"."protocols" ("id", "name", "created_at", "updated_at", "tags")
VALUES ('tcp', 'tcp', DEFAULT, DEFAULT, DEFAULT);
@@ -0,0 +1 @@
drop table ziti_edge.protocols;
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 entities
import "time"
type EntityStandardFields struct {
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Tags map[string]interface{} `json:"tags"`
}
type SessionEntity struct {
Token string `json:"token"`
EntityStandardFields
}
type Empty struct {
}
+89
View File
@@ -0,0 +1,89 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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.
*/
// Code generated by github.com/gobuffalo/packr. DO NOT EDIT.
package env
import "github.com/gobuffalo/packr"
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
func init() {
packr.PackJSONBytes("../embedded", "README.md", "\"H4sIAAAAAAAA/3RTPW/bPBDeBeg/PPCUvIilPcM7BB3aomiGpOh8Ik8Sa4p0j8ckzq8vSCl2W6SLBop3zycfZ5dwlPiDjSInTjjSQXA1qx7Tbd9PTuc8dCYu/RSHPI7kY38kc5BraAQvA1vozPj8cP+1bZKZeSGMzjOenc4u1J/CKXt1YcLgAonj1OF7lEM5KdfKpcR1LEH4Z3bCqW3KqAtJyXtSFwPiWNdVilmdd3qCictCwXZt0zZ7TBETK/YZ/2a+fsv9x5nBQZ3wDdhO3Fc9li2sEzYa5YSkko1mYbiE838XNFYuJgaV6D1L21RxJwwnWB4pe62k2uYxgkMqK3QmRYjgFxVCUj4mkDACc9k6RgGVlSkvLKtcl5BiFlOgLN+0ze4uO2+LdYS7FfGqMJlJLJ7pdL0DJdho8sJBK9fNNXOQ83k1tG1cKqnbDrUIC1NIK8kyQVnnWGlQOG3pbMvecaptlpwUkn+H+ysl0KgsIK8sLkw92SKkF17iU1FUMbrNs3c33SDQk5tIGVsC/atTd+6wxKgoSPzCJivflmX/l96tsWP/Wk4+KdIcs7dIhlaYzeUyK1zuXlR32P23r+PdFHebE8/OewzcNhMHFtLVxUuPHz7ef/vyAQPDzGwOb6XZYK5ootLti8K2ES42cbA1nHS9pULe85Mj5XSuRsIoccFM1TWNb89kZZ/D+nb+MK77FQAA///ALYpD6wMAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/definitions.all.json", "\"H4sIAAAAAAAA/+xV247aMBB95yssl8eQCwJU5Q8qVaLviEoGT4KRY0e2ozaL+PeVvUCu7AaW1b7wgJBnPOfM5WR8GCGEx4ziGOGdMXkcBC/MsAnQFHwBJpGFoKr0mQz0dgcZ0QGFhAlmmBTaJ5z7ey0F9ixOzYNjZKERwoak1cmeyxwsm9zsYWtcoLMTSl0o4X+UzEEZBjYuIVzD5VJOjAElGjfOyAjhv/4h8ubhcdwwW3BRLhMco1XNiBpXGrmJgnPccB69YZHaKCZS7LX9GRO/QaRmh2MUdb3k/8U7DWc/76IWRbYB9S3UGyk5ENFqWe20HrWtb/8nAieSiet6Vymri1ROg1k/NfPUDMKCZNC3WFrtbNQyraz1GuaLDvJ1NZ7xO2q8ncjt3Q8LSKTKiLGeomC0v7BoIN8dZT2UXht168xuwf7k1IZREaVIuUx+0d53zXmrSigkpOC2fauKkxnIWmtorMAuG/yj/r4GjOJe7VcpXK/4lEin4C/P6MqMH9wYR9ObSk60/ifVoG+rPv1F7/SjMGygFxrUPatn9j76yP6OrwEAAP//qPbbZY8JAAA=\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/app-wan.patch.json", "\"H4sIAAAAAAAA/5yOMW7DMAxFd59CUDvG0u5TdC86sBYVM1AoVaRRpEXuXlABCs8ZNOiD//33OznnXyn5xflNtS0x/pDSjOmMgVFz3Tn1W6AaZd3wChKhtfkbWEIDXbdwkcr+ZBi9NTRO/bzgqo8MUiKlylDeem3YlVD84jIUwXHQ8Wunjibw/jGSdjw0P+c8wxX/f2bcMdtUwkw8BiRAKUPmJR7SaM2Z91L86N5PD6DCWZ4DWvMInOzdp78AAAD//8KiKM5JAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/app-wan.post.json", "\"H4sIAAAAAAAA/7SQsU4DMQyG93sKKzC2yX5vwAQ76mAuTusqTULsAx1V3x0lFHSICSSGDLHyf//nnAcAc8vejGAOqmV07o2Vt+T3ZBNpyHPydbGcnUwHOqE4LGX7iklsyaL2KDmZTaPoUqhh8tORJv2YofesnBPGh5oLVWUSM0LAKNQfVHqeuVLrfxwAAEzCE/UwgGFPSbmHrhOh+sITiRkAdp1Q1uDzivF5awtWCk3NU+DUhcRijF3+xq2mrid77PLTYUX0FHCO2rR3m9/XYK243Ic7L9/Lvtb7/yrFvfztj1pym+YYr8ChncvwHgAA//9FZjxASwIAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/app-wan.put.json", "\"H4sIAAAAAAAA/6yQMU8DMQyF9/wKKzDSZL9/wAQ7YjAXp3V1TULsAx3V/XeUQKXrCOqQIdZ737Pf2QDYew52AHtQLYP3X6y8o7Anl0hjnlOoi+PsZTzQCcVjKbtPTOLKrO4oOdmHBtGlUKPktyON+jPDEFg5J5yeay5UlUnsABEnoS6o9D5zpRb/YgAAbMITdXND4l6sAXjt2rJFnDfqy69dUim2JQJFTj1aHE5TX/POb6a+O7tt/U3jQEn5iv8nItaKy1N8DHLNFaofPN6a2rv5F/HSKsBq2lvNdwAAAP//qAL8IgUCAAA=\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/association.patch.json", "\"H4sIAAAAAAAA/0yPsU4EMQxE+3yFFSgh6fcPqKBHFGbjsD6FJNi+Yjndv6PkhG6LFBmP540vDsA/cvIL+M2sLzH+svEzpS8KlSy3c02yB25R142+USOqtpXRuNXQ0dYtnLRV/zSSbO80otrniVa7aZgSDzOWN2mdxJjUL5CxKE2D0M+ZhUaHdwcA4DmpdwAfc9yPW5e74f8zDhDKA5soc50wDVjKLPYQD2pEEdxf88sNAHB1413dXwAAAP//8OXaSQgBAAA=\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/association.post.json", "\"H4sIAAAAAAAA/0yOsVIDMQxEe3+FxlCC3d8fUEHPUIizTJQ5LCMpxZHJvzN2hkkKFVqtdt85AMRHLnGBeHDvS86/7PxM5YtSI69yakX3xJJtPdA3WkYzWRmdpaUu5ulo0uLTCPK900iSzyOtftWwFB5e3N5UOqkzWVyg4mY0DUo/J1YaCO8BACBysRgAPua533+db4b/ZfAr1VFbqHKbZZZw2ybYQ75TM6ri/lpfrgUAlzDmEv4CAAD//ws9008HAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/association.put.json", "\"H4sIAAAAAAAA/0yOsU4EMQxE+3yFFSgh6fcPqKBHFGbjcD6FJNjeYjndv6PkhG4LFx6PZ97FAfhHTn4BfzLrS4y/bPxM6YtCJcttq0n2wC3qeqJv1IiqbWU0bjX0zcJZW/VPI8f2TiOofZ5ptZuGKfGwYnmT1kmMSf0CGYvSNAj9bCw0CN4dAIDnpN4BfMxzP35d7ob/ZeAL5VGbKHOdZRqwlAn2EA9qRBHcX/PLrQDg6sZc3V8AAAD//y7+fUwGAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/ca.patch.json", "\"H4sIAAAAAAAA/7SQMU/EMAyF9/4KKzDeNXu3E7oZdsTgNu41J9euEnc40P13lLaCihHBkMFPed979kcF4B5jcA24wWxqvH+PFo8ULlQLWa+zhHSro/rcDTRi9h3mekLrhvqaVdyhEOw2UUFoe6XOVg1DiBZVkF+STpQsUnYN9MiZlg/TXi5FAJzgSF9TqZaoL+BAfZQFl2tkXqIf/E71xXmUmdkt3vthBcZ8mk2f8CxJmUcSOwu2TGGfstV/3WYA16oy4brdJn2zAd5+ZDyb/XfEabbh77mGl/y7exfn/t5VeffqMwAA//+mWpq/UQIAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/ca.post.json", "\"H4sIAAAAAAAA/7SRMU/7MBDF93yKk///kSYFMaBsFeqGRHfEcI0viSvnHOyzRKn63ZFNQ0PZEAwZ8nTv9+75DgWA+m+0qkH1ImNdVW9GzIJ0RyWTtC6y9vvSuCo0PQ0YqgZDObog5S44VlcJIPuREsFtd9TIh4ZaGzGO0W68G8mLoaBqaNEGygOeXqLxlKKfCgAAxThQNgOohrxsaFAFwHMeH+eUw8ww/aUintq0h6bWcE4PJVqbN/1XzdQqO7PteBE4w021gnjD3WkzADUYfiDupFc1XJ9VfP1Ub5a3d8sveBNWUdw9rtk7awdiWTNuba7/LXDrnCXkc6KmFqOV6f0uyI8ifwNeRel/iybYhZ/dKjkXHK09HaxI37F4DwAA//+fGiQMuwIAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/ca.put.json", "\"H4sIAAAAAAAA/6RRv07zMBDf8xQnf98I8Z6tQp1hRwyX+NK4cnzBPg+l6rsjX4mIkACJDpHi8/3++twAmP/emQ7MJLJ01r558ffkDtRGkpFLdOnUerZ5mGjGbAfM7VKkPWaO5q7i5bRQJeD+SINcZ+icF88Rw1PihZJ4yqaDEUMmXUj0WnyiqvzcAACYiDMpuFLiIa//Pu+K8APuY+IQZoqyj9gHcp8LjyI/3u+KTOuwAXhRB8vW2HnjYT3VahKNNZqj0UcNlFsMQcP/s5upVaTCLr/63gis3fXMgTB+WAZVxBJk7ewL8zeBbyfeNnUrmz7in8pU5JWrqd+leQ8AAP//UVrJf6kCAAA=\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/cluster.patch.json", "\"H4sIAAAAAAAA/5SOMW4DIRBF+z0FIilt6PcU6aMUk2XwjoWBMEPhRL57NGwU0bqg4OvP++9nMca+UrCrsbtIXb3/JqEzhgu6jBJLz6HdHRXP2443YL+lzoKNXQXZdnflku1JMXKvqJzyecVNjgxCIKGSIb21UrEJIdvVREiMo9Dwq1NDFXhfjPkYYZ27qmiMzXDD/59KN4y6FjBSHhvsIKXh8+Kn1OvlOfeU7Lh9nA6gwIVnYMAIPYlGf5WnVhQ3ryz6Hr8BAAD//+eicvRgAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/cluster.post.json", "\"H4sIAAAAAAAA/5SOsW4DIRBE+/uKFUlpQ39fkT5KQY7BXgsDYZfCsfzvEWefdG0KCobhvblPROadg5nJnFXr7NwvKx8RTrAZGkvPod0sFyfLGVcvbkldFE1sLaL2IiWbw6DorWJgyvcFiz4zHwIrl+zTRysVTRliZoo+CdZCw0/nhuH/nIiITPZXmInoa32v+2/3XWO7jfkNcYgDIudVJ9antE57c7vUbWyix+HJUn+SPSsg+p50RK/KvwQDd8w9pZdlGufxFwAA//99O2dVZQEAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/cluster.put.json", "\"H4sIAAAAAAAA/5SPsU6EIRCEe55ig5YK/f8U9sYCf5a7vXCA7FKcl//dzaKX0FpQMJn5ZvZuAOwzRbuBPYu0zftvEnrFeEJXUFIdJfabo+p5P+M1sN/zYMHOrg1xF67FvihEbg2VUj8vuMuvFmIkoVpCfuu1YRdCthukkBmnoePXoI5a/24AAGwJV5xhRYYTWwPwMb1tRdwX9+Onl3RMOiJiojKr2YWc58wnv6h+JmfsWNsWVsQURhaV/iz/KniMBziMvuMnAAD//5RMgrlrAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/device.post.json", "\"H4sIAAAAAAAA/xTMUQrCMAwG4PedogQfNXnfbbT5dRnYFBOFKt599ALfbymFTqa0Ftoy+yrytbQL9AFuyLu/m74Gm0vUDc9riOJjFcHdI3kPb3SeSI6OqfhtR01a/kcAAAD//x01+a9bAAAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/edge-router.patch.json", "\"H4sIAAAAAAAA/5yOsVIEIRBE8/0KCg1vId8/MDO3DHAZbueKA5wZgtO6f7eGtawNTDQgoKf7dX9OxthHjHYxdhNpi/cfKDhDPIMrIKn2EunmsHpeN7gG9nqaqXYBYteCrJu7cC32pCi5NVBWfbvAKrsWYkTBWkJ+ptqABIHtYlLIDMNA8N6RQEe8TMa8DrEdvTrTGFvCFX5+OpwgaVuEhGV0sAs5jz0P/qB6Tc6l52xH9n7agWvuLEBP8X9UjL8wJZz5iIuQQs+i0rflTx2KO7ZM+u5fAQAA//8WElk4uAEAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/edge-router.post.json", "\"H4sIAAAAAAAA/5yPsVIDIRCG+3uKHbT0oL83sLN3LPBYks0QQHYpYibv7iyeM+dYmYKCZb/v/7lOAOaRglnAHEXq4twnCc0YDmgzSiw9h3axVByvRzx7dvo0t9IFG9taWOyJSzZPapJLRVWV9xOu8j3zIZBQyT69tFKxCSGbBaJPjGOh4UenhtrhdQIAMNmfccAAZk2dBdtzMBPA2wDq3nPdIT83/VPDqE0CRsojn61PaXR9cLupG+TAbn8i7xJS+K0Tf+C9KWD0PYmOtpV/6VU3557SljLpuX0FAAD//+AN+DrJAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/edge-router.put.json", "\"H4sIAAAAAAAA/5yQsVIDIRCG+3uKHbQ00N8b2Nk7FngsyWYI4O5SxMy9uwN6M+dYmYKCn/0/PrhNAOaRgpnBnFTr7NwnKR0wHNFm1FhaDny1VJwsJ7x4cf3owKUpstja1J6lZPPUQXqt2Enl/YyLfmc+BFIq2acXLhVZCcXMEH0SHAOMH40Yu8LrBABgsr/gKAOYJTVR5OewBeqPYiaAt1Gue+ZtV992/XmMsVsFjJSHi1if0vB+cLvUjeaorX+uvwtI4TduyO9IAaNvSXv0M/Iv/PYXAOvU1/oVAAD//3HhTrXPAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/enroll-er.post.json", "\"H4sIAAAAAAAA/6yOMU/DQAyF9/wKy2JsE4rEkrUrAztiOHKviav07rBdRKn631FCgSIxdvDgT8+f37Ei4huJ3BIP7qVtmg9xWSL2qBN8k/cp6qGW3Fg3YBesQdI8jktoXbJ5vbWceDFp/FAwefLLFp1/sRCjuOQUxkfNBeoC45Y2YTTMAcXrXhRTgaeKiIgN+gZdQ31tOluIuDuvFdHzfFcudce/oW9w0clcJfVnGxHvJD0g9T5wS6tfGt5/6P3q7nbmp8V/va78o5rmVH0GAAD//7gFLdmPAQAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/enroll-updb.post.json", "\"H4sIAAAAAAAA/1yPsU5DMQxF93yFFRhpsr+vYEcMae1HXYU42H5Cpeq/owQhPXXIcnVy7/EtAMRnxrhAPLv3Jecfdj4QflBq5KtsDfWaWLKdzvRZLFNTqfWwdTymLubpYtLiyyjya6fRJMcLnfwvK4jsLK3UV5VO6kwWF1hLNZqA0tfGSkPhLQAAxF7MvkUxBoD3yfT919sD9Z+MS5TWIYC0cpuzlkqtU/Ep79K83wC4h/Hu4TcAAP//d/S3Zw8BAAA=\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/enroller.post.json", "\"H4sIAAAAAAAA/0yOvU5DMQyF9zyFFRhpst+nYEcMoXGoK9cOie9QUN8dOUjoDh58dH6+nwAQn6nGDeLFrG85f5PRCesnJkFruksd90Sa5/mCtzIzylDm1HVauk6V+OIddu/oJfpxxbP9aaVWMlIp/Dq04zDCGTdohScuw8CvnQb6+lsAeF9iP3qdDyBKueH/58QDm69VbCRrY6bCvHie8kHNnjzJzhxX9hH8HuE3AAD//6UoH7X4AAAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/identity.patch.json", "\"H4sIAAAAAAAA/5SSwWrzMBCE734Kof8/OvY9t0IfoFB6Cj0o1jjZIEuutC6kxe9eVnESG0JCDj7soJn52PVvoZT+T1avld4z9+u6/iGmFewOlQe3YfA2HisKdWr26EyqycIzMSFVveFmXx1S8LqUID72kKSwPaDhk2asJabgjXuLoUcUo16r1riE/CDia6AIQdgUSn1msZ+/FUiltDcdLpNgR7TSZtGSzx2pMs5lnn/1TK3FufKDczp7x/IUOOFeAqd5M81K6cSR/E6XV+WaMqFmFX7ols6PhDj3veKbGsyVd8QsncOWaGaX5mgWrRkcizSWzy9A4m4sgNKL7cjf38E2BAfjHy/hypiPu2jqETtKSWDut5kYzfFxFzG6edLTv8M5cryBv5luUcg3FsVfAAAA//+M6eDXJQMAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/identity.post.json", "\"H4sIAAAAAAAA/5ySzW7qMBCF93kKy/cuIdmzq9QHqFR1hViYeAKDnHFqjytRlHevbPLjoCwQK8ixzzdnxnMrhJD/UcudkGfmbldVv8i4BX2CkoAbG0i7a4m28vUZWuUr1ECMjODLznouL96S3EQOXzuIIHu8QM13TWmNjJaU+XC2Axd9cicaZTykCw6+AzqICfaFEEJIUi0k84gc/nfgWvQeLflRQv+mWyRZCHFItC4vcst441ds10ETY2pokFI4XypjUiP/qkytkjPZ+jxPxhpb9uyQTkMsISRQaKeOkvLlwU3nQsh3+MEacuUTXJIG5bAoDOSsMS0Qr5TPymSzHxQKxkzMKeDKoO5+5oWQ9Xi01oAiOZ31m4WvVo/O5yeNeh0bOn18nRo8uPkNE7vIf/uHNVp52LHpaXAaGhUMjzu84OQbusJSzqnrTEKG9mH6r6xmNrAs3H65PqxOi0jzxdvsfr56xG3nxYrT7Iu+KP4CAAD//0fsQ8dPBAAA\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/identity.put.json", "\"H4sIAAAAAAAA/5yRwWrzMBCE736KRf9/TO17boU+QKH0FHJQonWywZbU3XUhDXn3IjWKZcilPRiksfabGenSAJj/5MwazFE1rrvui5Se0B2w9ah9mLzjc0uhk/0RRysdOfRKSihtnLQ9SfBmlTB6jpg4YXfCvf5o1jlSCt4Orxwichoza+jtIJgPMH5MxJgCbBoAAOPtiHm4IMvaHqSsSZ7dSL5sI/JIIhS8mAZgm8mxNrxU7LJLzRn7FNlhTz4HldYOQy71r6vULk/msWudrWKV+qJM/nCLBmDQT+O9XVbeBfn+H8C84CftsVbekLN0U7ZL43QRlbHD3k6DJum6+n2zjFsYlNt9UG4XwoDWz+1m8/ymC079LA9YltmeZxIpjvW5Pz7P3X4RbnO7wiZ916b5DgAA//8RE1iv+AIAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/network-session.post.json", "\"H4sIAAAAAAAA/2yOQU7DQAxF9zmFZbFsk31uwBkQizTz07iE8WA7oIJ6dzQJQgh1++z//v9qiPhBEvfEc0Tpu+5TQo5IZ7QZMemak11b0c7HGa+DdxnxofZydLiLZm+LerQX18yHaotrQdXp6YIxdlZMCywEzj3VTiJ22LuMeEy/6E/YwySfecO3wx6Y1aPCO+8n1QXDPmA7JEzDugT3NA2LY/c0Py42vK1iqM1P/8c0RM/N7TsAAP//mTg9SRgBAAA=\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/policy.post.json", "\"H4sIAAAAAAAA/xTMwQ1CIQwA0DtTkMajtne2UahSorSx9YDG3X9Y4P1SznCSBiVDj7BC9JWQC7cH4+S462e290JR8tr5dXUyfUoVdjT1wOE64byVWMab0dvgGpD+RwAAAP//NHeypVwAAAA=\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/service.patch.json", "\"H4sIAAAAAAAA/7SSwU7EIBCG730Kgh53y70338B43XjAMm1nwwIyU5PV9N0NbK10d6MxcQ9N4c/wzf8PfFRCyHs0shFyYA6NUu/IuAXTQ+2AOz86E481ekXtAAdNiiC+YQtUB83tUO/JO7lJGD4GSBz/soeWT5o2Bhm90/Yx+gCREUg2otOWIBdEeB0xQjKwq4R4zmIoa5NFIaTTB1h2yXSELnUz0KHLPajW1mY/d6pQVTq5daO1Mp+dNiegcVTyZve7eS9WOWblGzI7zepvGXPRWc414UrerA+e+Cz3da9ZJY7o+sLvhefcdVlPRbLgI9+oS1X+v4YPfQSiJz8yxJ9v4aLf+hbWVGeCR8cPxiT8/4FZ9yuagU6PNk9smeIfXmTClS+ySt/0GQAA//9rBDXdjAMAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/service.post.json", "\"H4sIAAAAAAAA/6SSvW7rMAyFdz+FoHvHxt6zdczUomuRQbVoh4EiqSRdIA387oXUxJBbB/0bDFuUzneoY54qpfR/tHqt9E4krpvmFQVXYHuoPUgXBm/pWGNouN3BwXDDQC/YAtcxsNR7Dl7fJIocIyRMeNpDK+81Yy0KBm/cPYUIJAis16ozjiEfIHgekCD5P1ZKKaW9OUAWK6Wt58sn9ATMD2EQoKnmbQzo5dbatKkrpbaZGkuzU8G9rNKlCbrUroUOfW6Sa+NcvtC/pqg2WZll49l4F1g2ln+HM0TmeNdtLK/84NycnG5cUBcyzfWvcs2HPmU79V5knGsxkOjzcjvpF1KcE8pq0SwLoe/1tDV+dLqiQy/QAxXCqnyPS5OwkFVpP16ZlG/KWjewAP35R8+pYvoZ0UJnBpdjmaL6gU3ClZNUpWd8CwAA//869whk2QMAAA==\"")
packr.PackJSONBytes("../embedded", "api-schema/entities/service.put.json", "\"H4sIAAAAAAAA/5RRTU/DMAy991dEgSNr77vxDxBXtENY3M5T5oTYRRpT/zuK90EGnYBD1frV7z0/+9AYY+/R26WxG5G07LoPFFyAH6AlkD6O5PO+xdjxegM7xx1Dfsc1cJtGabccyT4UEdknKCrxdQtrOWLOexSM5MJTjgmyILBdmt4FBm3I8DZihmL/0hhjjCW3AyUbYz3x+RPIp4gkj95n4C94KNVzHAWybYxZqWqqzQ6V7rkqmTP0ZVwPPZIOya0LQQPddRXaKVNpUzVXJTWTXPHf0mvTjw0ouoks1SYUSzGLPZWrC38m67VCjVbDsmSkwV5+Td+dbvCQBIbjsk/Epn5Pc4eZ2VVtP9248R9p4oarXg+9G4MGuIT6x7mL3ILGEE4uTXmmzwAAAP//Jaq6WigDAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20180828083820_init-schema.down.sql", "\"H4sIAAAAAAAA/1SOwcrCMBCE74G8wx4C+f+LL1A81GRqAzaVJmJvJWjUHNqC9uTTSygI7mGXZfebGd2SEJztsDeWM86ITEXojfPuL2+5HA5Qnl6XRxzDMIUxUtW1DaXpNj/HsKR5GtbjZh1LoHONDj/IluQ7LWmI13uUWfo/N1/Drj7ooU4eJHXXHsmpGk1JX4JU6VSpIYv8DavJVEUODKs5E6L4BAAA//+QzumVyQAAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20180828083820_init-schema.up.sql", "\"H4sIAAAAAAAA/2SOv2rDMBDGd4He4QaB2qUvYDqo0udaUMtFUqk3I1q10WAbEk95+qAYAiE33B/ufvd9ZiAhOHvDu3WccUZkW8JoQwxPdaoR8AEd6fRzyHOaljRnav3QU1n+1uOctrIu07582cuW6LuDxx3ySvJctjLl3/8s6+vnmmIHt+tghP6KIGn88ElBd+gV3QjSKmhlIJt6DWfItk01DGc4E+Laaw8V8cA2lwAAAP//BuaXKOUAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20180905100347_initial-tables.down.sql", "\"H4sIAAAAAAAA/wEAAP//AAAAAAAAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20180905100347_initial-tables.up.sql", "\"H4sIAAAAAAAA/+xa32/bNhB+D5D/gW91gGRwgmbYuicvUQZjmbO5NrA+EYxEO2xkUpDoOOmw/32wLZmk+EMyRRtDEfWlNcnj6e77vuNRvRlHg0kEJoNf7yPwjXACcTLHP8wxgzmeE0YL0Ds9AYAkoHqm0+Et+HM8/GMw/gJ+j74A8zN6mIDR9P7+fL2cogUuf39BefyE8t7V9fWZZam2PM4x4jiBiANOFrjgaJGBFeFPm3+Cb4xicBvdDab3E0DZqnemLl9mSZflHM2L0quvBaMOp+vO76x++OffD+DTp836x9OTs19OT9Z/LNGPUXF6Ug+76VkuSdIhFaZHpKffOj0zQuc4z3JCucvk9eVV+4zjnMMMLyxzOX41buUySQr4gnMyI9gY1EfGUowas6uY3NqLESeMQs6esbrc58VJAdGSMxgjiGnO0nSBKYeYosd07bePl6SAjHObRV+TaMmfJCvK42NS4rjp6cj7UCYlLTA9QfWhVIfpaPjXNALD0W30N4hRASW6QbgkNMGva9ceRqqEgJ40sUFxSIIpJ/wN8rcMG8QnkOZf9tuLyvep+aacqsGHJLFmVZ0JeiRRrFqySkwZDZJXNbv7lAzlTSBJtp60j3KlQgmeoWXKIUoWhO50p0rBDKWFodhpUrZZXD0+RjTxCiJXQQTqWPAlraBLZNgO7ifR2IlaAAa3t+DmYfR5Mh4MRxN1M402s2dw9zCOhr+NNmDu1VF2BsbRXTSORjfR5xa0clCrwEVhPB17UUo+OngRoXoBTyLJ8O2K3K6gDYpXM8Kq5BnwVQ1BKaQOXDVBihhUWiHPbr8NBKzsEXDbzHOqfpwuC47z98bt0EVca5oRxyv0ZtAE0CkHZUKhanHT+7V/I0fv55NYc8snznlXHx2mGrq8ts2du7HzPAswmhKqzvOxJC5QYJ2Be7yZ1K9JJaKDla+rXWT3jbJkpQ2rm63g14zkuPCxYu4Vw7WI4TrDI1SzSnYM1awagkJC9GImxiy1TBQU98GtjSMKL3RflGGLO8rdZEMHlOWMs5iluibvRK9/5tLj92LoWQxn6DEncXWifg/+ka+Qt+E3H0UCNP1eRwaBiHVNlDHQOomMzsh8NxYklKaCEqCSBCghR6gdJUwMil2OQDVrumKr4xbJVsVAtNfW5jp/IbFBN4KA1we7CS3gEyv41pKA7sefXMfcuoWM5eUxg1CO5zhvm1qZP2U4fNr80kLJokOxpzt5unPnKJcIW5AaLxG2Q3CXMitvGigjseXiAkzQY4oBKUCCsxzH64gBVAA2A1f9y58v+j9eXPabSAVrR6Lq5zWsZEwpwa3W1HW7mtQqUmJne8jEHAizZ4XnPeHpuexQ+J3FRnraxJglb0K7DuGa9N66b3JQzM5JuW8rwlC9TpLgskGL7aJCGWwHj2ojRyCqKW5wSA1N4G2Piox9HAvc31lBQTFfsfwZWq+/O95++1S10hffu+86ovdcXsf8vsu/y7bEDOw6eAzArk+BIrkmxlVjVsZVKHUzzs+xI0iBj2OBpcD00ULb0/3xQheN5o8YKMvgClHlw3WDtuhH/oaT/v+NiA0UdFNPK7buwELt/waIAWAo7doXxna13bSbBmF9kl7fxZxz2ZdD7H2AL39hHRQ/6f6JMYt7FbFaFPvKluh06kDRcWI7H7aDiaOnqk9xQ0RS4MDb7v5yUOH38SwwLP4LAAD//7ldsnUaLAAA\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20181016142716_default-data.down.sql", "\"H4sIAAAAAAAA/wEAAP//AAAAAAAAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20181016142716_default-data.up.sql", "\"H4sIAAAAAAAA/8zXzW4bNxAH8LsBv8NCFyeAp+DHkMNBT0biAmkTt6jtFujFGJJDeQFHUqVViuTpi1XswmncpI7zsSeK0OXH4fyH0rOT0+Nfz7pnJ2c/d7M3/dBfaJ3r7LtZX3Ux9MPri+H1Sjez7tGsr7PDbraQlzquZa0yaL2QYdxtV/Vm93h/77ej5+fHp92jg0BkDTYHVr0AMhIIOwLyyWfRlgvLwWF3cL7R9cFh9/T4h6Pz52f/fHj8/f7e/t4XJubgW0aGYLMFlEQgqAhaqzDZKExmJD7VV33Rb4QsWGNsXKGpTYAWPaRQPRB6xyWgSy6PyFNdf5pytV4Oy7K8ug/wsJsNMt+8Ax2uNqPj7fJvw9fG/Lnty6i5Xr81ZyirXW12y0cwHwQ1yeu+3Luh7jK9GWAlm81wuV5u55cj75d3tp9as89LXMjQvxq7+uCPfui7k5vtw3R3wG6xL95+U5aL1s/vjW4to80WoQbPgDUbSDFY0GJrdGiScf79ar9/Hf95wjv65QMHnuvyYq3zfrl46G2IUVccN2B2DhB9AElCEGpulCs7TzSe5IkspEr3RBfDWq4ecFefkV4wMpMHa0IBrGqBm7fQWiuGCBO13RA9P+1+180wCbPz0SBxhVJMAnQOIdcQAX0WieSk+d3r9KOsZDEJsdZqYjINvGQEZI3ANQcoMbmQsmAydRQfbTdjZ/QyCXVwVnzNDMmiAUwBQSg5iLbkUH1Tg7u8ni63w2V39FLXfZmGPHl0UqQCKjKg4QQJq0Jy3DSHUH3YdchPy7VOQ5xDiC7bAK1pBmRrIDeLIMabSI2LOL3O4Yu+/jWVKEZt6pIEIEQFjFghWyRAQ05bpsjN3Jp8kxkh1pTYIiZgl2kMpIOsHKFF5wOpBHJtdB9v18uVTsdNjjG6YMG2QoDZZ0ghJiiBK5LzSgZ3g6RNJotk0KcaPTBlD5iyA9EcIJsYhYOLGOKtUh/LREqt3nONzEAtWEDlAuKKQLLkjBpqluN1IncD8GPor0A2JqRIxkMkTICFEiQ2FbSJs7E5q83fSuNkSh19I2dMBiJGQIljLkVAfNTqfdKSd/+IjzYTeRkbWy1FPMSW3FjoDMmyBRVjfCxKOch1b0ymyGjZsVKCGAkByYzNUR1wZo8qHJNzo/lFX+vVhHIYqjHocobYagDkwMBGCGIuQkadb3STw//1w/rvAAAA///sRr2Y3REAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190111165646_add-gateway-protocols.down.sql", "\"H4sIAAAAAAAA/3IJ8g9QCHF08nFVqMosyYxPTUlP1UtPLEktT6yMLyjKL8lPzs8pBgQAAP//QZ+dpyYAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190111165646_add-gateway-protocols.up.sql", "\"H4sIAAAAAAAA/6ySwW7iMBCG73mKuZFIq9UupxZObmKqqMFBxjlwstzEDS4QI9s0KlXfvSJSQipKS9XOzRp/vz95JqQYMQwM3SQY9sopLotS/i2Fk7V45lujnc712nq+B6AK6CrL4ghmNJ4iuoA7vICPi6QMSJYkfzyANvQQ0+BfVx9vXQ78kzD5Uhh/+C+4FFdVaaS1fGfWHX71/3r4SUAfz40UThZcOACnNtI6sdlCrdyyOcJeVxIiPEFZwqDStR+8w3fb4ie4E6VtrR6trr7xdV3q4OV1AKNRw997wdjzUMIwvWD2ACiKIEzJnFEUEwYnV/hxtpw/rGCSUhzfkmYx/GMvAIonmGIS4vnpixZ8VfyyV29pTsV6zTNmXVCrdsatkq7WZsWttFbpykJE0xmEaZJNCeTrnXXScFWMvbcAAAD//8IbvktwAwAA\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190116092021_add-gateway-hostname.down.sql", "\"H4sIAAAAAAAA/3L0CXENUghxdPJxVajKLMmMT01JT9VLTyxJLU+sLFZwCfIPUHD29wn19VPIyC8uyUvMTbUGBAAA//9B3XIDNAAAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190116092021_add-gateway-hostname.up.sql", "\"H4sIAAAAAAAA/3L0CXENUghxdPJxVajKLMmMT01JT9VLTyxJLU+sLObl4nR0cVHIyC8uyUvMTVUIcwxy9nAM0jA1NNK0BgQAAP//KjPQ4zsAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190429142248_add-enrollment-table.down.sql", "\"H4sIAAAAAAAA/wEAAP//AAAAAAAAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190429142248_add-enrollment-table.up.sql", "\"H4sIAAAAAAAA/7SS0UrrQBCG7wt5h7lrAodDFfSiPsyyzf5pxmxmw+7EmorvLrGJplC0gv53AzPffLtMGWEVpHbnQUdWNnB7/IfE4H0L0ZSt8mxFRMSOPtL3y+qbSFCS3vsTZkwZJGm0LEqLTaZrMHw2jekitzYO1GD4N1tAlHUw7P7IYrHBmKo5F4qoECEl0uK3pglGmiQ1NJBp4snGsrYxv9lsiqslJ04LrYM759ze3f+Yg+eOI5KxSqTcIqltOzqw1u8lHYPgGs7pWtzXHIfK9l5JwiEvLnP6zv0KR+0+zaqPKcjFN8yc9cvrersd23aLYygestVbAAAA//+VbEHtBQMAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190502145944_add-authenticators-table.down.sql", "\"H4sIAAAAAAAA/0opyi9QKElMyklVqMosyYxPTUlP1UssLclIzSvJTE4syS8qtgYEAAD//4CpwcskAAAA\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190502145944_add-authenticators-table.up.sql", "\"H4sIAAAAAAAA/7SRQUvEMBCF74X+h3fbFsSDoIf1x4TZZLod2yYlmbh0xf8uu22lgiKC+25DHt97mbGRSRlKh55xFhXD7sj3lLVlr2JJQ0xlUZUFAIjDp3LeTr/KB4XPfT+DLrLBJ40kXvE1zowdTxijDBQndDzdrekXj05G3C3TNzHGNB0iNxzZW06bBS0m4bSUG1jbsFR6pWhbitXD41P9h3ILaT6JM6SAysBJaRhxEm2vI87BMxw3lHuFD6eq/omUR/dPJKVjWp9eUvDf/mMl7d7ed9jvr8bDZvX1c1mUxUcAAAD//y7wWpdyAgAA\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190525131603_add-network-session-certs.down.sql", "\"H4sIAAAAAAAA/0opyi9QKElMyklVqMosyYxPTUlP1ctLLSnPL8qOL04tLs7Mz4tPTi0qKbYGBAAA//82ylRcKwAAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190525131603_add-network-session-certs.up.sql", "\"H4sIAAAAAAAA/6SRz26zQAzE70i8g28h0qdPaqVe0odZOawhLuBFtgklVd+9okRpS1Opf3xjPDP8tC6V0Akc9y3BiZ0DxZr+C/mYtAlGZpwklKRueVbkGQAARxgGjiDJQYa2XdR5yiTmiiwOVytC39D0Zp+nV+5QJ2ho+rds1slf/y6sRAscQ6iajwBKFSlJSfb1A9gZbW4Fp0e/wJwXFUtN2uuMckQtD6jF3c3tdu07YssxVJo6cO7IHLseRvbD6yeckhBEqnBor8Q8/Si03DYG9G/E0lh8gh36+LcCx9rgwZJcbJun581uN0v7d/fc3ufZSwAAAP//NcYjcYoCAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190710140702_add_event_log.down.sql", "\"H4sIAAAAAAAA/0opyi9QKElMyklVqMosyYxPTUlP1UstS80ric/JTy+2BgQAAP//TM0ZTiAAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190710140702_add_event_log.up.sql", "\"H4sIAAAAAAAA/6zTwU6zQBAH8HuTvsPcCsmX72Cih/owmyk7pWthluz+aaXGdzcWKmDVCPZ/g+z+dmaSyYIwhMCbQujk4IzYXP7LQRSm8HlcLpLlgojIWRqnrq9+/RT1IK2LotXek3mNCOwU1L9nqr00VAVXcmhoL82/9gKaSkbegUO245Dc3T+kE57vOM7ggxmiU73P1nAY8y1RODTDwv5sDQqbb219KBkQa0qJkXMhyDN+YXxY4/m3nokITvPzgZt4lsHdgalebw2QNk/R62YW1a6XNXwpBa6UCC4rOjrszp908ipkZct1AVJ/TNLvuqwre0sOnMerRr9s6cKtXl5XtF53E+lXOn18CwAA//8UNjN5SQQAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190711152500_add-service-hosts.down.sql", "\"H4sIAAAAAAAA/0opyi9QCHF08nFVqMosyYxPTUlP1StOLSrLTE6Nz8gvLim2BgQAAP//Ai0zFCMAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190711152500_add-service-hosts.up.sql", "\"H4sIAAAAAAAA/6yR0UrDMBiF7/sU57KD4QvsKrZ/JZilkqbgrsJcsxlaktEERZ9eVibbdOjNbnMO5/vIXyhimqDZvSB8uuSM7Xb2LtrxzW2seQ0xReQZAHy/uQ5oW15C1hqyFWI+xa6zPrn0ccgv4my2yDImNKm/MdMKK0sUtWy0YlxqXBSM2fd4UnzJ1AqPtEJ+Upqf828GPO0bs+1R1Yr4g/zJnkFRRYpkQc1vVkR+Q6P9GFLYhOGq0vkXXHc6Ntx/Vt6m9zD2JtoYXfDxKCXapQSvptPSM290g4OW8zu8hDDYtUdJFWuFxnY9RLvIvgIAAP//QNzU42ACAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190715170856_remove_fabrics.down.sql", "\"H4sIAAAAAAAA/wEAAP//AAAAAAAAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190715170856_remove_fabrics.up.sql", "\"H4sIAAAAAAAA/5yQwUrEQBBE74H8Q99WLyLiQcjHDJ2ZnsmwY3fo7llZv142YkAPYjxX1auisDkpOM6N4L16DZQKPRjppUaycQAASCorRGFzxcoOX2rIOGuNoaYQ8nkahwOw1l8Z9vw0Dv9OR+Fcy58JmBI0KhivYUUzX1R6WWAWaZAoY28OGZsRsDhwb+0QmjitUtkDpqRkBhfUuKDePT0+v9wfQ5UbIKj0m/8bZ9+2Tz51PrO88WmrGIftqJ8dn4/Z9Ksc/LqSTR8BAAD//2RoilsZAgAA\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190729141132_remove-gw-status-col.down.sql", "\"H4sIAAAAAAAA/wTA0QnDMBAD0P9AdtAEXaDDBKVWjEG9g/pMaafPo0sfFE8L/1HjUOt6dJa+/E2wNbzS6x0Y88jwCOHMtBiILMSy0XRxuXDRU89927c7AAD//0gyQH5XAAAA\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190729141132_remove-gw-status-col.up.sql", "\"H4sIAAAAAAAA/0rMKUktUihJTMpJVajKLMmMT01JT9VLTyxJLU+sLFZIKcovUEjOzynNzVPILI7Pz8vJzEu1BgQAAP//kEeLtzUAAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190731134126_drop-gateway-protocols.down.sql", "\"H4sIAAAAAAAA/7TSX2vyMBQG8PtCv8O5s4WXlykMNneV2TjKujpieuFViE1Ws7WNJHEyx777sMMq6v7I2Lkrp/31afMMCEYUA0XXCYaVcopJUcj/BXdyyV/Y3Ginc11a3wt8DwBACWgny+II7kl8h8gEbvEEjk86opBmSfLvQ9jYa6kRvp89YZNqTTxzk8+4CXpn4QmCqgsjrWULU7bCRfey94WxJ+RGcicF4w7AqUpax6s5LJWbNZew0rWECA9RllCo9TII94XFXPxScLywm3iPVtcn/MkW7ry+daDfb56f+l545Xu+hxKKyU9KsSZRFMFglI4pQXFK4eAutj1wxh6eYDgiOL5Jm8IE210IBA8xwekAjw9faiFQ4i/C7XTpMN3O8pN4LbTNx0snDTg+LeWx7+BCQK7LRVXDTFtX80pC28Hzbi98DwAA///4Px00kwMAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190731134126_drop-gateway-protocols.up.sql", "\"H4sIAAAAAAAA/0opyi9QKElMyklVqMosyYxPTUlP1UtPLEktT6yMLyjKL8lPzs8ptubl4uVKzClJLcKltlgBbFJyfk5pbp5CRn5xSV5ibqo1IAAA//94qxQtXwAAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190826091630_remove-service-protocol.down.sql", "\"H4sIAAAAAAAA/5SRywqDMBBF9/mKWSpIKd36MSGNow2mmTAZLfbrSyw+QOhjlsnck3OJZTSCIObqEZ5OnMamw1NCHp1FHZmELPmkCgUAsJy7BobBNRBIIAzez5d5LIUkbFwQODD0lta67ddMHsYWGYPFdLRI1by6cPLjo2F7M1xczuWfDjvKbxJr9G3xEa7jBozs7oYn6HGCYmte7XuUqqyVMl6Qv38B0CPkPYJISTrGVCv1CgAA//8X4WOAwAEAAA==\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20190826091630_remove-service-protocol.up.sql", "\"H4sIAAAAAAAA/0opyi9QKElMyklVqMosyYxPTUlP1StOLSrLTE6NLyjKL8lPzs8ptgYEAAD//wnoAAMnAAAA\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20191008091800_remove-protocol.down.sql", "\"H4sIAAAAAAAA/8SQQUvDMBzF7/0Uj1zawhgi7DJPVSMUayddKuw0Yhu2yNrU9j8HE7+7pNQOIu4i6P/y+g6/8su7yXgkOER0nXAcNem1Kjdq2rSGTGF2HQIPAHSJr3uTbbGVbXB5EeIxix+ibIV7voJz6UIgzZNk0vO1rJTLz2ahC/3IF62SpMq1JJCuVEeyanDQtO0rjqZWuOV3UZ4I1OYQhA6/b8pf8SQ33SD20pn6jLfrP/7Wf//wMZ/3/LMXXnlenC55JhCnYgE2bs+mbFyfIWC6ZBMwO6DN0xC2nZ5lm5VkofcUJTlfIvBp1/kTDDFofP/4C5PXvS6sypD/6kJF06/SxxmTzwAAAP//G933TRoDAAA=\"")
packr.PackJSONBytes("../embedded", "db/postgres/migrations/20191008091800_remove-protocol.up.sql", "\"H4sIAAAAAAAA/0opyi9QKElMyklVqMosyYxPTUlP1Ssoyi/JT87PKbYGBAAA//+IBLuXHwAAAA==\"")
packr.PackJSONBytes("../embedded", "db/sqlite3/migrations/20180905100347_initial-tables.down.sql", "\"H4sIAAAAAAAA/wEAAP//AAAAAAAAAAA=\"")
packr.PackJSONBytes("../embedded", "db/sqlite3/migrations/20180905100347_initial-tables.up.sql", "\"H4sIAAAAAAAA/+xazW7jNhC+B8g78LY2kEM22AAFenITpTWaOq0bF90TwUi0w12ZFEgqTlD03Qv9k9Lox5QW3XbN3ETOkJz55pvhODdrb/HoocfFD/ce2lGBJd0xwRWanZ8hxAJUjM1meYt+XS9/Waw/op+9jwgeq4dHtNrc318k4pzsaf79hUj/mcjZ1fX1vEW0Ie5LSjQNMNEoIJpqtqfo1rtbbO4fkR9LSbnGyUelyT6yReMocBXVZKfy03xSgncctn7ocod3f/397vxs/v35WfJnmdgn6vysbltoxDELRtgbGpUPLgf7YMv4jspIMq67VF6/vxruVio1jui+Za2mr+BWXSqZwi9Usi2joFGfhAgp6XWlpTLT5xPNBMdafKa2uMvFmcIk1gL7BFMuRRjuExhSTp7C5Nwup2QKC63bNLqqJLF+NrRYw0WlEcjQGBHcU6gzAh4aU5BATgGb1fK3jYeWq1vvz4QJsBFdGMeMB/Q1OdHDKplFM2MaJBMWUK6ZfsP6LaIAr0zE2e8vh/PF/4KzISMzyMCTmNk29jHkbPkfsyA7yfDLF/Ee0C2JQ41JsGe8jPDCMlsSKiCtNEgjFS6Gi5IGTYwmhtFUMAZNqca7h7W3/HGVwGJWd9ccrb07b+2tbrzfa7E8Y8G8pYJIyDlZ6hMtJE6SabNYc4JkeQJHIJmlwh+L9c1Pi7UrdYzy/BinT+lvNDMsCvmaUYVMR0NZAvB2R9YAsdHIIkP3MY4/ZB/ztv3AjaPg6SsBbqyoLPjYBbiKhGUl4iIfEaUOQgau8qfA6QV0ijZcOLoTzTkwi7XH7ZBAYYj2ZN1xmocGY75BXzCW88a7QWgN1TguL1EzJFP54dDIoGU8uJzKUILLTYfuX8h+OlTvCpfHKH2NmKTKDqmus36r4dsUTN1miaTvoGaoZyBWVCmwWeWURkzMORXSI9PQN4uDRqFrkWHh5KwJU+O+CgHpLIARP4yVpvLU0BzrXjACd0TTA3kDIhCNMnTuNWxrdMkkcE/UxXtwK7TKTlcfOlT1dD+H5pnuhqfjy13wkHF7nYum6tcDXA+zI25m1CMGIY/QYqTzY61saIFCt1/KKAX6pOAe6TSt0Wk6otPmhirC7Xxf0jVYIFgYswSt367aCoZICi18ETb5qiSEy3kXV52yQVc22JInyfysg3Wy8Jf5ATGzMZxwJ2hEOyXGyu0J85uOHuwpwbdsV86Nppoam46k0ZH8OS1x2ta2ONCOv/YWsqLyhflAjE6CIRcIBVzhZ6F0pqlC0IfvumqquoZIyDzvMa7pjsqhJjdhnJvDqQOeacjBPDGIx2F4HIS/CIJB8Ha+9lPU4loSLz4nfjOdZrdYc5k6P1mLTNTPKrUXpjRQk1QrrftUQQZKmTpNMeN2fYawn7aGHVIztL2nmpOt9zZqtKmuPaTsa2EtTvVByM+4teszsunjEvH5WVxbPnWfHSle9+qx4v/l8ggCZOGLGiALuPxLMIaaWnUsg82tJuDbmlwkivCBcKt73xMNzQTek7e/Fuj0gAYES/kV5NPcerjxnx7VBAJos9HvbSfVStGFKQYAy7l1Xm1hyRXIaM8mhWCB+Obdm5frzDWtNzdCbao4HH7rfwIAAP//CFvkqekqAAA=\"")
packr.PackJSONBytes("../embedded", "db/sqlite3/migrations/20181016142716_default-data.down.sql", "\"H4sIAAAAAAAA/wEAAP//AAAAAAAAAAA=\"")
packr.PackJSONBytes("../embedded", "db/sqlite3/migrations/20181016142716_default-data.up.sql", "\"H4sIAAAAAAAA/6SWT28bNxDF7wb8HRa+OAE8AP8MORz0ZCQ+pG3Sok5aoBdjSA7jBRxJXa0SpJ++WFlpZEdNUq0uFLXQ+817+5bSi1fXV7+97l68ev1Ld9ZXXYz9+PFm/LjS9Vn35KyvZxfd2ULe6dnT05PfL39+c3XdPTkPRNZgc2DVCyAjgbAjIJ98Fm25sJxfdOdv1jqcP/3h9OT05EhQDr5lZAg2W0BJBIKKoLUKk43CZCbQc33fF52FKlhjbFyhqU2AFj2kUD0QescloEsuT6hrHb7B6r/gXDya4+b+Wr++qdpkczfeSH3XL3af3b/fn60K2cJoIVDMgNkJsDQHwadIJVarJZxfnJ50u9f583vZ7nKSenDl+27d5y+Mw0YfbQ84l814O/krMi6Hm82q5r0I/rV+v92sdfiUylruxmldyXr9YTnUB641sQumOCA0BTC3AkmlQMOsFkuyMZQH3v53TPJFPA83B5yuhuW4LMu7r1VpvFtPTZmWIxX+2vRlktiux05RVtspymqn8EijSR768s3n4u8Rprsz3g7LzdvbSfHXve2B2b5fdyFj/14nyT/7se9e3W//U/LAU7XHmtq1r99aRpstQg2eAWs2kGKwoMXW6NAk4/xjNxfdY7sHk3ury5tB3/bLxdcMilFXHDdgdg4QfQBJQhBqbpQrO080EZ/JQqp0z3QxDnJ3yP538gpGZvJgTSiAVS1w8xZaa8UQYaK2PcDeXHd/6Ho8HuR8NEhcoRSTAJ1DyDVEQJ9FIjlpfnso/ygrWRyP0VpNTKaBl4yArBG45gAlJhdSFkymTpjLzXoKrpfjUcFZ8TUzJIsGMAUEoeQg2pJD9U0NbrtyvdyMt93lOx36MgOXPDopUgEVGdBwgoRVITlumkOoPmwD/Gk56AxMDiG6bAO0phmQrYHcLIIYbyI1LuJ0V4iXff0wqxNRm7okAQhRASNWyBYJ0JDTlilyM3tln1dAa0psEROwyzQ1w0FWjtCi84FUArk2wa42w3KlM2HkGKMLFmwrBJh9hhRighK4IjmvZHBbwzavFGTQpxo9MGUPmLID0RwgmxiFg4sY4p6pK5ljSr3nGpmBWrCAygXEFYFkyRk11CzHXTW2nd+SjuAYE1Ik4yESJsBCCRKbCtrE2dic1eb3ajHPVPSNnDEZiBgBJU4FEQHxUav3SUve/iu9XM85KxpbLUU8xJbcZClDsmxBxRgfi1IOsotunh207FgpQYyEgGSm7KoDzuxRhWNybgK97Gu9m1uIUI1BlzPEVgMgBwY2QhBzETLqfKNPhfj8Q/VPAAAA//9bssy2PQwAAA==\"")
}
+508
View File
@@ -0,0 +1,508 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
import (
"github.com/netfoundry/ziti-edge/edge/controller/apierror"
edgeconfig "github.com/netfoundry/ziti-edge/edge/controller/config"
"github.com/netfoundry/ziti-edge/edge/controller/internal/permissions"
"github.com/netfoundry/ziti-edge/edge/controller/middleware"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/persistence"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/netfoundry/ziti-edge/edge/controller/util"
"github.com/netfoundry/ziti-edge/edge/internal/cert"
"github.com/netfoundry/ziti-edge/edge/internal/jwt"
"github.com/netfoundry/ziti-edge/edge/migration"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/xctrl"
"github.com/netfoundry/ziti-fabric/fabric/xmgmt"
"github.com/netfoundry/ziti-edge/sdk/ziti/config"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls"
"fmt"
jwt2 "github.com/dgrijalva/jwt-go"
"github.com/gobuffalo/packr"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/common/constants"
"github.com/xeipuuv/gojsonschema"
"gopkg.in/oleiade/reflections.v1"
"io/ioutil"
"net/http"
"path/filepath"
"reflect"
"strings"
)
type AppEnv struct {
db *gorm.DB
dbWithPreload *gorm.DB
Stores *migration.Stores
BoltStores *persistence.Stores
Handlers *model.Handlers
Embedded *packr.Box
Schemes *Schemes
Config *edgeconfig.Config
EnrollmentJwtGenerator jwt.EnrollmentGenerator
Versions *config.Versions
AuthHeaderName string
AuthCookieName string
ApiServerCsrSigner cert.Signer
ApiClientCsrSigner cert.Signer
ControlClientCsrSigner cert.Signer
FingerprintGenerator cert.FingerprintGenerator
RootRouter *mux.Router
RequestResponderFactory response.RequestResponderFactory
RequestResponder interface{}
ModelHandlers *migration.ModelHandlers
AuthRegistry model.AuthRegistry
EnrollRegistry model.EnrollmentRegistry
Broker *Broker
HostController HostController
}
func (ae *AppEnv) GetApiServerCsrSigner() cert.Signer {
return ae.ApiServerCsrSigner
}
func (ae *AppEnv) GetControlClientCsrSigner() cert.Signer {
return ae.ControlClientCsrSigner
}
func (ae *AppEnv) GetHostController() model.HostController {
return ae.HostController
}
func (ae *AppEnv) GetSchemas() model.Schemas {
return ae.Schemes
}
func (ae *AppEnv) GetHandlers() *model.Handlers {
return ae.Handlers
}
func (ae *AppEnv) GetConfig() *edgeconfig.Config {
return ae.Config
}
func (ae *AppEnv) GetEnrollmentJwtGenerator() jwt.EnrollmentGenerator {
return ae.EnrollmentJwtGenerator
}
func (ae *AppEnv) GetDbProvider() persistence.DbProvider {
return ae.HostController.GetNetwork()
}
func (ae *AppEnv) GetStores() *persistence.Stores {
return ae.BoltStores
}
func (ae *AppEnv) GetDbStores() *migration.Stores {
return ae.Stores
}
func (ae *AppEnv) GetAuthRegistry() model.AuthRegistry {
return ae.AuthRegistry
}
func (ae *AppEnv) GetEnrollRegistry() model.EnrollmentRegistry {
return ae.EnrollRegistry
}
func (ae *AppEnv) ClusterHasEdgeRouterOnline(clusterId string) bool {
return ae.Broker.ClusterHasEdgeRouterOnline(clusterId)
}
func (ae *AppEnv) GetApiClientCsrSigner() cert.Signer {
return ae.ApiClientCsrSigner
}
type HostController interface {
RegisterXctrl(x xctrl.Xctrl) error
RegisterXmgmt(x xmgmt.Xmgmt) error
GetNetwork() *network.Network
}
type Schemes struct {
AppWan *BasicEntitySchema
Association *BasicEntitySchema
Ca *BasicEntitySchema
Cluster *BasicEntitySchema
Device *BasicEntitySchema
Enroller *BasicEntitySchema
EnrollEr *BasicEntitySchema
EnrollUpdb *BasicEntitySchema
EdgeRouter *BasicEntitySchema
Identity *BasicEntitySchema
NetworkSession *BasicEntitySchema
Policy *BasicEntitySchema
Service *BasicEntitySchema
FabricConfigs *FabricConfigSchemas
}
func (s Schemes) GetEnrollErPost() *gojsonschema.Schema {
return s.EnrollEr.Post
}
func (s Schemes) GetEnrollUpdbPost() *gojsonschema.Schema {
return s.EnrollUpdb.Post
}
type FabricConfigSchemas struct {
Native *gojsonschema.Schema
Passthrough *gojsonschema.Schema
Local *gojsonschema.Schema
}
type BasicEntitySchema struct {
Post *gojsonschema.Schema
Patch *gojsonschema.Schema
Put *gojsonschema.Schema
}
type AppHandler func(ae *AppEnv, rc *response.RequestContext)
type AppMiddleware func(*AppEnv, http.Handler) http.Handler
func NewAppEnv(c *edgeconfig.Config) *AppEnv {
// See README.md in /ziti/edge/embedded for details
// This path is relative to source location of this file
embedded := packr.NewBox("../embedded")
c.Persistence.AddMigrationBox(&embedded)
ae := &AppEnv{
Config: c,
db: nil,
dbWithPreload: nil,
Embedded: &embedded,
Stores: nil,
Versions: &config.Versions{
Api: "1.0.0",
EnrollmentApi: "1.0.0",
},
AuthCookieName: constants.ZitiSession,
AuthHeaderName: constants.ZitiSession,
RootRouter: mux.NewRouter(),
RequestResponderFactory: response.NewRequestResponder,
ModelHandlers: migration.GetModelHandlers(),
AuthRegistry: &model.AuthProcessorRegistryImpl{},
EnrollRegistry: &model.EnrollmentRegistryImpl{},
}
sm := getJwtSigningMethod(c.Api.Identity.ServerCert())
key := c.Api.Identity.ServerCert().PrivateKey
ae.EnrollmentJwtGenerator = jwt.NewJwtIdentityEnrollmentGenerator(ae.Config.Api.Advertise, sm, key)
ae.ApiClientCsrSigner = cert.NewClientSigner(ae.Config.Enrollment.SigningCert.Cert().Leaf, ae.Config.Enrollment.SigningCert.Cert().PrivateKey)
ae.ApiServerCsrSigner = cert.NewServerSigner(ae.Config.Enrollment.SigningCert.Cert().Leaf, ae.Config.Enrollment.SigningCert.Cert().PrivateKey)
ae.ControlClientCsrSigner = cert.NewClientSigner(ae.Config.Enrollment.SigningCert.Cert().Leaf, ae.Config.Enrollment.SigningCert.Cert().PrivateKey)
ae.Schemes = &Schemes{
FabricConfigs: &FabricConfigSchemas{},
}
err := ae.LoadSchemas()
ae.FingerprintGenerator = cert.NewFingerprintGenerator()
if err != nil {
log := pfxlog.Logger()
log.WithField("cause", err).Fatal("could not load schemas")
}
return ae
}
func (ae *AppEnv) InitPersistence() error {
var err error
ae.db = InitPersistence(&ae.Config.Persistence)
if ae.db != nil {
ae.dbWithPreload = ae.db.New().Set("gorm:auto_preload", true)
ae.Stores = migration.NewGormStores(ae.db, ae.dbWithPreload)
}
ae.BoltStores, err = persistence.NewBoltStores(ae.HostController.GetNetwork())
if err == nil {
err = persistence.RunMigrations(ae.HostController.GetNetwork(), ae.BoltStores, ae.Stores)
}
if err == nil {
ae.Handlers = model.InitHandlers(ae)
}
return err
}
func getJwtSigningMethod(cert *tls.Certificate) jwt2.SigningMethod {
var sm jwt2.SigningMethod = jwt2.SigningMethodNone
switch cert.Leaf.PublicKey.(type) {
case *ecdsa.PublicKey:
key := cert.Leaf.PublicKey.(*ecdsa.PublicKey)
switch key.Params().BitSize {
case jwt2.SigningMethodES256.CurveBits:
sm = jwt2.SigningMethodES256
case jwt2.SigningMethodES384.CurveBits:
sm = jwt2.SigningMethodES384
case jwt2.SigningMethodES512.CurveBits:
sm = jwt2.SigningMethodES512
default:
pfxlog.Logger().Panic("unsupported EC key size: ", key.Params().BitSize)
}
case *rsa.PublicKey:
sm = jwt2.SigningMethodRS256
default:
pfxlog.Logger().Panic("unknown certificate type, unable to determine signing method")
}
return sm
}
func (ae *AppEnv) getSessionTokenFromRequest(r *http.Request) string {
token := r.Header.Get(ae.AuthHeaderName)
if token == "" {
sessionCookie, _ := r.Cookie(ae.AuthCookieName)
if sessionCookie != nil {
token = sessionCookie.Value
}
}
return token
}
func (ae *AppEnv) Db() *gorm.DB {
return ae.db.New()
}
func (ae *AppEnv) DbWithPreload() *gorm.DB {
return ae.dbWithPreload.New()
}
func (ae *AppEnv) WrapHandler(f AppHandler, prs ...permissions.Resolver) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rid := uuid.New()
sw, ok := w.(*middleware.StatusWriter)
if ok {
rid = sw.RequestId
}
rc := &response.RequestContext{
Id: rid,
Identity: nil,
Session: nil,
ActivePermissions: []string{},
ResponseWriter: w,
Request: r,
EventLogger: &DefaultEventLogger{Ae: ae},
}
rc.RequestResponder = ae.RequestResponderFactory(rc)
sessionToken := ae.getSessionTokenFromRequest(r)
log := pfxlog.Logger()
_, err := uuid.Parse(sessionToken)
if err != nil {
log.WithError(err).Debug("failed to parse session id")
sessionToken = ""
} else {
log.Tracef("authorizing request using session id '%v'", sessionToken)
}
if sessionToken != "" {
rc.Session, err = ae.GetHandlers().ApiSession.HandleReadByToken(sessionToken)
if err != nil {
//don't error on "not found", just an un-authed session, rely on permissions below
//error on anything else as we failed to work with the store
if !util.IsErrNotFoundErr(err) {
log.WithError(err).Debug("error requesting session")
rc.RequestResponder.RespondWithError(err)
return
}
}
}
//updates updatedAt for session timeouts
if rc.Session != nil {
err := ae.GetHandlers().ApiSession.HandleUpdate(rc.Session)
if err != nil && !util.IsErrNotFoundErr(err) {
log.WithError(err).Debug("failed to update session activity")
rc.RequestResponder.RespondWithError(err)
return
}
}
if rc.Session != nil {
rc.Identity, err = ae.GetHandlers().Identity.HandleRead(rc.Session.IdentityId)
if err != nil {
if util.IsErrNotFoundErr(err) {
apiErr := apierror.NewUnauthorized()
apiErr.Cause = fmt.Errorf("associated identity %s not found", rc.Session.IdentityId)
apiErr.AppendCause = true
rc.RequestResponder.RespondWithApiError(apiErr)
} else {
rc.RequestResponder.RespondWithError(err)
}
return
}
}
if rc.Identity != nil {
rc.ActivePermissions = append(rc.ActivePermissions, permissions.AuthenticatedPermission)
if rc.Identity.IsAdmin {
rc.ActivePermissions = append(rc.ActivePermissions, permissions.AdminPermission)
}
}
for _, pr := range prs {
if !pr.IsAllowed(rc.ActivePermissions...) {
rc.RequestResponder.RespondWithUnauthorizedError(rc)
return
}
}
f(ae, rc)
}
}
func (ae *AppEnv) WrapMiddleware(f AppMiddleware) mux.MiddlewareFunc {
return func(h http.Handler) http.Handler {
return f(ae, h)
}
}
func (ae *AppEnv) GetEmbeddedFileContent(filePath string) (string, error) {
file, err := ae.Embedded.Open(filePath)
if err != nil {
return "", err
}
fileContents, err := ioutil.ReadAll(file)
if err != nil {
return "", err
}
return string(fileContents), err
}
func (ae *AppEnv) LoadSchemas() error {
// add custom format checkers
gojsonschema.FormatCheckers.Add("uuid", gojsonschema.UUIDFormatChecker{})
defContent, err := ae.GetEmbeddedFileContent("api-schema/definitions.all.json")
if err != nil {
panic(err.Error())
}
defLoader := gojsonschema.NewStringLoader(defContent)
return ae.Embedded.WalkPrefix("api-schema/entities/", func(s string, f packr.File) error {
if err != nil {
panic(err.Error())
}
fileName := filepath.Base(s)
fileNameParts := strings.Split(fileName, ".")
if len(fileNameParts) < 3 {
return fmt.Errorf("schema file name '%s' is invalid, ensture <entity.method>.<ext> format and packing", fileName)
}
entityName := fileNameParts[0]
entityPropertyName := kebabToCamelCase(entityName)
method := kebabToCamelCase(fileNameParts[1])
schemaContent, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
}
entitySchemaLoader := gojsonschema.NewStringLoader(string(schemaContent))
schemaLoader := gojsonschema.NewSchemaLoader()
if err := schemaLoader.AddSchemas(defLoader); err != nil {
pfxlog.Logger().WithField("cause", err).Panic("error adding definitions schema")
}
schema, err := schemaLoader.Compile(entitySchemaLoader)
if err != nil {
pfxlog.Logger().WithField("cause", err).Panic("error compiling schema")
}
if hasProp, _ := reflections.HasField(ae.Schemes, entityPropertyName); !hasProp {
return fmt.Errorf("found schema with property name '%s' from file '%s', no matching schema property", entityPropertyName, fileName)
}
entitySchema, err := reflections.GetField(ae.Schemes, entityPropertyName)
if entitySchema == nil || reflect.ValueOf(entitySchema).IsNil() {
entitySchema = &BasicEntitySchema{}
err = reflections.SetField(ae.Schemes, entityPropertyName, entitySchema)
if err != nil {
return fmt.Errorf("found schema with property name '%s', could not set basic schema", entityPropertyName)
}
}
if err != nil {
return err
}
err = reflections.SetField(entitySchema, method, schema)
if err != nil {
return fmt.Errorf("found schema with property name '%s', could not set methods '%s'", entityPropertyName, method)
}
return nil
})
}
func kebabToCamelCase(kebab string) (camelCase string) {
isToUpper := true
for _, runeValue := range kebab {
if isToUpper {
camelCase += strings.ToUpper(string(runeValue))
isToUpper = false
} else {
if runeValue == '-' {
isToUpper = true
} else {
camelCase += string(runeValue)
}
}
}
return
}
+182
View File
@@ -0,0 +1,182 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
import (
"github.com/netfoundry/ziti-edge/edge/controller/internal/permissions"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/netfoundry/ziti-edge/edge/migration"
"github.com/gobuffalo/packr"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"net/http"
)
const (
ClientCertHeader = "X-Client-CertPem"
EdgeRouterProxyRequest = "X-Edge-Router-Proxy-Request"
)
type AuthStore interface {
BaseLoadOneByAuthenticatorId(authId string, pl *migration.Preloads) (migration.BaseDbModel, error)
migration.Store
}
type AuthModuler interface {
Registerer
Migrater
Authenticator
Enroller
}
type AuthHandlerFunc func(ae *AppEnv, rc *response.RequestContext, hc *HandlerContext)
type HandlerContext struct {
TargetIdentity *model.Identity
Data map[string]interface{}
}
type Migrater interface {
GetMigrations() []*packr.Box
}
type EnrollmentContextOld struct {
Enrollment *migration.Enrollment
Authenticator *migration.Authenticator
Identity *migration.Identity
Method string //dupes Enrollment.Method, but Enrollment is not always defined
}
type EnrollmentContext struct {
Enrollment *model.Enrollment
Authenticator *model.Authenticator
Identity *model.Identity
Method string //dupes Enrollment.Method, but Enrollment is not always defined
}
type Enroller interface {
ProcessEnrollmentConfig(ae *AppEnv, method string, config interface{}) (interface{}, error)
Enroll(ae *AppEnv, rc *response.RequestContext, ec *EnrollmentContextOld)
CreateEnrollment(ae *AppEnv, ec *EnrollmentContextOld, tx *gorm.DB) error
IsIdentityEnrollment(m string) bool
IsEnrollerForMethod(m string) bool
RenderEnrollmentDetails(e *migration.Enrollment) (interface{}, error)
}
type Authenticator interface {
IsAuthenticatorForMethod(string) bool
IsAuthorized(*AppEnv, *http.Request) (string, error)
RenderAuthenticatorDetails(e *migration.Authenticator) (interface{}, error)
AuthStore() AuthStore
Fingerprints(auth *migration.Authenticator) ([]string, error)
}
type Registerer interface {
Register(ae *AppEnv, identityRouter *mux.Router, currentUserRouter *mux.Router, idType response.IdType)
}
type Registry struct {
enrollers []Enroller
authenticators []Authenticator
migraters []Migrater
registerers []Registerer
}
func (r *Registry) AddModule(a AuthModuler) {
r.AddEnroller(a)
r.AddAuthenticator(a)
r.AddMigrater(a)
r.AddRegisterer(a)
}
func (r *Registry) AddMigrater(m Migrater) {
r.migraters = append(r.migraters, m)
}
func (r *Registry) AddRegisterer(rer Registerer) {
r.registerers = append(r.registerers, rer)
}
func (r *Registry) AddAuthenticator(a Authenticator) {
r.authenticators = append(r.authenticators, a)
}
func (r *Registry) AddEnroller(e Enroller) {
r.enrollers = append(r.enrollers, e)
}
func (r *Registry) GetAuthenticatorByMethod(m string) Authenticator {
for _, a := range r.authenticators {
if a.IsAuthenticatorForMethod(m) {
return a
}
}
return nil
}
func (r *Registry) GetEnrollerByMethod(m string) Enroller {
for _, a := range r.enrollers {
if a.IsEnrollerForMethod(m) {
return a
}
}
return nil
}
func (r *Registry) GetAuthenticators() []Authenticator {
return r.authenticators
}
func (r *Registry) GetEnrollers() []Enroller {
return r.enrollers
}
func (r *Registry) GetMigraters() []Migrater {
return r.migraters
}
func (r *Registry) GetRegisterers() []Registerer {
return r.registerers
}
func WrapByIdHandler(ae *AppEnv, f AuthHandlerFunc, idType response.IdType, prs ...permissions.Resolver) http.HandlerFunc {
return ae.WrapHandler(func(ae *AppEnv, rc *response.RequestContext) {
id, err := rc.GetIdFromRequest(idType)
if err != nil {
rc.RequestResponder.RespondWithNotFound()
return
}
i, err := ae.GetHandlers().Identity.HandleRead(id)
if err != nil {
rc.RequestResponder.RespondWithNotFound()
return
}
hc := &HandlerContext{
TargetIdentity: i,
Data: map[string]interface{}{},
}
f(ae, rc, hc)
}, prs...)
}
+60
View File
@@ -0,0 +1,60 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
import (
"github.com/Jeffail/gabs"
)
type EnvInfo struct {
Arch string `json:"arch"`
Os string `json:"os"`
OsRelease string `json:"osRelease"`
OsVersion string `json:"osVersion"`
}
type SdkInfo struct {
Branch string `json:"branch"`
Revision string `json:"revision"`
Type string `json:"type"`
Version string `json:"version"`
}
type Info struct {
EnvInfo *EnvInfo `json:"envInfo"`
SdkInfo *SdkInfo `json:"sdkInfo"`
}
func ParseInfoFromBody(body []byte) (map[interface{}]interface{}, error) {
container, err := gabs.ParseJSON(body)
if err != nil {
return nil, err
}
return ParseInfoFromContainer(container)
}
func ParseInfoFromContainer(container *gabs.Container) (map[interface{}]interface{}, error) {
info := map[interface{}]interface{}{}
envInfo := container.Search("envInfo")
sdkInfo := container.Search("sdkInfo")
info["envInfo"] = envInfo.Data()
info["sdkInfo"] = sdkInfo.Data()
return info, nil
}
+874
View File
@@ -0,0 +1,874 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
import (
"github.com/netfoundry/ziti-foundation/common/version"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/persistence"
"github.com/netfoundry/ziti-edge/edge/migration"
"github.com/netfoundry/ziti-edge/edge/pb/edge_ctrl_pb"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-foundation/storage/boltz"
"bytes"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/kataras/go-events"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/pkg/errors"
"go.etcd.io/bbolt"
"reflect"
"strings"
"sync"
"time"
)
type Broker struct {
ae *AppEnv
events map[events.EventEmmiter]map[events.EventName][]events.Listener
clusterMap *clusterMap
}
type clusterMap struct {
internalClusterMap *sync.Map
}
const (
SessionRemovedType = int32(edge_ctrl_pb.ContentType_SessionRemovedType)
SessionAddedType = int32(edge_ctrl_pb.ContentType_SessionAddedType)
SessionUpdatedType = int32(edge_ctrl_pb.ContentType_SessionUpdatedType)
ApiSessionHeartbeatType = int32(edge_ctrl_pb.ContentType_ApiSessionHeartbeatType)
ApiSessionRemovedType = int32(edge_ctrl_pb.ContentType_ApiSessionRemovedType)
ApiSessionAddedType = int32(edge_ctrl_pb.ContentType_ApiSessionAddedType)
ApiSessionUpdatedType = int32(edge_ctrl_pb.ContentType_ApiSessionUpdatedType)
ServerHelloType = int32(edge_ctrl_pb.ContentType_ServerHelloType)
ClientHelloType = int32(edge_ctrl_pb.ContentType_ClientHelloType)
ErrorType = int32(edge_ctrl_pb.ContentType_ErrorType)
)
func (m *clusterMap) getEntryMap(clusterId string) *sync.Map {
if value, ok := m.internalClusterMap.Load(clusterId); ok {
edgeRouterMap, ok := value.(*sync.Map)
if !ok {
panic("could not convert edge router map to *sync.Map")
}
return edgeRouterMap
}
return nil
}
func (m *clusterMap) AddEntry(clusterId string, edgeRouterEntry *edgeRouterEntry) {
edgeRouterMap := m.getEntryMap(clusterId)
if edgeRouterMap == nil {
edgeRouterMap = &sync.Map{}
m.internalClusterMap.Store(clusterId, edgeRouterMap)
}
edgeRouterMap.Store(edgeRouterEntry.EdgeRouter.Id, edgeRouterEntry)
}
func (m *clusterMap) GetEntries(clusterId string) []*edgeRouterEntry {
var entries []*edgeRouterEntry
entryMap := m.getEntryMap(clusterId)
if entryMap == nil {
return nil
}
entryMap.Range(func(_, vi interface{}) bool {
if entry, ok := vi.(*edgeRouterEntry); ok {
entries = append(entries, entry)
} else {
panic("cluster/edge router map contains a non *edgeRouterEntry value")
}
return true
})
return entries
}
func (m *clusterMap) GetOnlineEntries(clusterId string) []*edgeRouterEntry {
var entries []*edgeRouterEntry
for _, entry := range m.GetEntries(clusterId) {
if !entry.Channel.IsClosed() {
entries = append(entries, entry)
}
}
return entries
}
func (m *clusterMap) RemoveEntry(clusterId, edgeRouterId string) {
if v, ok := m.internalClusterMap.Load(clusterId); ok {
if edgeRouterMap, ok := v.(*sync.Map); ok {
edgeRouterMap.Delete(edgeRouterId)
}
}
}
func (m *clusterMap) RangeEdgeRouterEntries(f func(entries *edgeRouterEntry) bool) {
m.internalClusterMap.Range(func(clusterId, value interface{}) bool {
if edgeRouterMap, ok := value.(*sync.Map); ok {
edgeRouterMap.Range(func(edgeRouterId, value interface{}) bool {
if edgeRouterEntry, ok := value.(*edgeRouterEntry); ok {
return f(edgeRouterEntry)
}
pfxlog.Logger().Panic("could not convert edge router entry")
return false
})
}
return true
})
}
type edgeRouterEntry struct {
EdgeRouter *model.EdgeRouter
Channel channel2.Channel
}
func listener2async(l events.Listener) events.Listener {
return func(args ...interface{}) {
go l(args...)
}
}
func NewBroker(ae *AppEnv) *Broker {
b := &Broker{
ae: ae,
clusterMap: &clusterMap{
internalClusterMap: &sync.Map{},
},
}
b.events = map[events.EventEmmiter]map[events.EventName][]events.Listener{
b.ae.GetStores().Session: {
boltz.EventDelete: []events.Listener{
b.sessionDeleteEventHandler,
},
boltz.EventCreate: []events.Listener{
b.sessionCreateEventHandler,
},
},
b.ae.GetStores().ApiSession: {
boltz.EventCreate: []events.Listener{
b.apiSessionCreateEventHandler,
},
boltz.EventDelete: []events.Listener{
b.apiSessionDeleteEventHandler,
},
},
}
b.registerEventHandlers()
ae.HostController.GetNetwork().AddRouterPresenceHandler(b)
return b
}
func (b *Broker) AddEdgeRouter(ch channel2.Channel, edgeRouter *model.EdgeRouter) {
if edgeRouter.ClusterId == "" {
pfxlog.Logger().Errorf("adding connecting edge router failed, edge router [%s] did not have a cluster id [%s]", edgeRouter.Id, edgeRouter.ClusterId)
return
}
edgeRouterEntry := &edgeRouterEntry{
EdgeRouter: edgeRouter,
Channel: ch,
}
b.clusterMap.AddEntry(edgeRouter.ClusterId, edgeRouterEntry)
sessionMsg, err := b.getCurrentSessions()
if err != nil {
pfxlog.Logger().WithField("cause", err).Error("could not get current sessions")
return
}
pfxlog.Logger().Infof("sending edge router session updates [%d]", len(sessionMsg.ApiSessions))
if buf, err := proto.Marshal(sessionMsg); err == nil {
if err = ch.Send(channel2.NewMessage(ApiSessionAddedType, buf)); err != nil {
pfxlog.Logger().WithField("cause", err).Error("error sending session added")
}
} else {
pfxlog.Logger().WithField("cause", err).Error("error sending session added, could not marshal message content")
}
networkSessionMsg, err := b.getCurrentStateNetworkSessions(edgeRouter.ClusterId)
if err != nil {
pfxlog.Logger().WithField("cause", err).Errorf("could not get current network sessions for cluster id [%s]", edgeRouter.ClusterId)
return
}
pfxlog.Logger().Infof("sending edge router network session updates [%d]", len(networkSessionMsg.Sessions))
if buf, err := proto.Marshal(networkSessionMsg); err == nil {
if err = ch.Send(channel2.NewMessage(SessionAddedType, buf)); err != nil {
pfxlog.Logger().WithField("cause", err).Error("error sending network session added")
}
} else {
pfxlog.Logger().WithField("cause", err).Error("error sending network session added, could not marshal message content")
}
pfxlog.Logger().Infof("edge router connection finalized and synchronized [%s] [%s]", edgeRouter.Id, edgeRouter.Name)
}
func (b *Broker) getEventDetails(args []interface{}) *migration.CrudEventDetails {
if args != nil && args[0] != nil {
if ed, ok := args[0].(*migration.CrudEventDetails); ok {
return ed
}
} else {
log := pfxlog.Logger()
log.Warn("event fired without the expected CrudEventDetails pointer as args[0]")
}
return nil
}
func (b *Broker) apiSessionCreateEventHandler(args ...interface{}) {
var apiSession *persistence.ApiSession
if len(args) == 1 {
apiSession, _ = args[0].(*persistence.ApiSession)
}
if apiSession == nil {
log := pfxlog.Logger()
log.Error("could not cast event args to event details")
return
}
b.sendApiSessionCreates(apiSession)
}
func (b *Broker) sendApiSessionCreates(apiSessions ...*persistence.ApiSession) {
apiSessionMsg := &edge_ctrl_pb.ApiSessionAdded{}
apiSessionMsg.IsFullState = false
for _, session := range apiSessions {
fingerprints, err := b.getApiSessionFingerprints(session.IdentityId)
if err != nil {
pfxlog.Logger().WithError(err).Errorf("could not get session fingerprints")
return
}
apiSessionMsg.ApiSessions = append(apiSessionMsg.ApiSessions, &edge_ctrl_pb.ApiSession{
Token: session.Token,
CertFingerprints: fingerprints,
})
}
byteMsg, err := proto.Marshal(apiSessionMsg)
if err != nil {
pfxlog.Logger().WithError(err).Errorf("could not marshal api session added message")
return
}
channelMsg := channel2.NewMessage(ApiSessionAddedType, byteMsg)
b.sendToAllEdgeRouters(channelMsg)
}
func (b *Broker) apiSessionUpdateEventHandler(args ...interface{}) {
var apiSession *persistence.ApiSession
if len(args) == 1 {
apiSession, _ = args[0].(*persistence.ApiSession)
}
if apiSession == nil {
log := pfxlog.Logger()
log.Error("could not cast event args to event details")
return
}
b.sendApiSessionUpdates(apiSession)
}
func (b *Broker) sendApiSessionUpdates(sessions ...*persistence.ApiSession) {
apiSessionMsg := &edge_ctrl_pb.ApiSessionUpdated{}
for _, session := range sessions {
fingerprints, err := b.getApiSessionFingerprints(session.IdentityId)
if err != nil {
pfxlog.Logger().WithError(err).Errorf("could not get session fingerprints")
return
}
apiSessionMsg.ApiSessions = append(apiSessionMsg.ApiSessions, &edge_ctrl_pb.ApiSession{
Token: session.Token,
CertFingerprints: fingerprints,
})
}
byteMsg, err := proto.Marshal(apiSessionMsg)
if err != nil {
pfxlog.Logger().WithError(err).Errorf("could not marshal session updated message")
return
}
channelMsg := channel2.NewMessage(ApiSessionUpdatedType, byteMsg)
b.sendToAllEdgeRouters(channelMsg)
}
func (b *Broker) sendToAllEdgeRouters(msg *channel2.Message) {
b.clusterMap.RangeEdgeRouterEntries(func(edgeRouterEntry *edgeRouterEntry) bool {
err := edgeRouterEntry.Channel.Send(msg)
if err != nil {
pfxlog.Logger().WithError(err).Errorf("could send session added message")
}
return true
})
}
func (b *Broker) apiSessionDeleteEventHandler(args ...interface{}) {
var apiSession *persistence.ApiSession
if len(args) == 1 {
apiSession, _ = args[0].(*persistence.ApiSession)
}
if apiSession == nil {
log := pfxlog.Logger()
log.Error("could not cast event args to event details")
return
}
b.sendApiSessionDeletes(apiSession)
}
func (b *Broker) sendApiSessionDeletes(sessions ...*persistence.ApiSession) {
apiSessionMsg := &edge_ctrl_pb.ApiSessionRemoved{}
for _, session := range sessions {
apiSessionMsg.Tokens = append(apiSessionMsg.Tokens, session.Token)
}
byteMsg, err := proto.Marshal(apiSessionMsg)
if err != nil {
pfxlog.Logger().WithError(err).Errorf("could not marshal session removed message")
return
}
channelMsg := channel2.NewMessage(ApiSessionRemovedType, byteMsg)
b.sendToAllEdgeRouters(channelMsg)
}
func (b *Broker) syncIdentitiesEventHandler(eventName events.EventName, args ...interface{}) {
ed := b.getEventDetails(args)
if ed == nil {
log := pfxlog.Logger()
log.Error("could not cast event args to event details")
return
}
log := pfxlog.Logger()
err := b.ae.GetDbProvider().GetDb().Update(func(tx *bbolt.Tx) error {
ctx := boltz.NewMutateContext(tx)
for _, e := range ed.Entities {
if eventName == migration.EventCreate || eventName == migration.EventUpdate {
log.Debugf("creating bolt identity to mirror gorm identity: %v", e.GetId())
gormIdentity, ok := e.(*migration.Identity)
if !ok {
return errors.Errorf("unable to cast event entity to Identity. id: %v, type: %v", e.GetId(), reflect.TypeOf(e))
}
identity := &persistence.Identity{
BaseEdgeEntityImpl: persistence.BaseEdgeEntityImpl{
Id: gormIdentity.ID,
EdgeEntityFields: persistence.EdgeEntityFields{
CreatedAt: *gormIdentity.CreatedAt,
UpdatedAt: *gormIdentity.UpdatedAt,
Tags: *gormIdentity.Tags,
Migrate: true,
},
},
Name: *gormIdentity.Name,
IsDefaultAdmin: *gormIdentity.IsDefaultAdmin,
IsAdmin: *gormIdentity.IsAdmin,
}
if eventName == migration.EventCreate {
if err := b.ae.GetStores().Identity.Create(ctx, identity); err != nil {
return err
}
} else {
if err := b.ae.GetStores().Identity.Update(ctx, identity, nil); err != nil {
return err
}
}
}
if eventName == migration.EventDelete {
log.Debugf("deleting bolt identity to mirror gorm identity: %v", e.GetId())
if err := b.ae.GetStores().Identity.DeleteById(ctx, e.GetId()); err != nil {
return err
}
}
}
return nil
})
if err != nil {
log.Errorf("Failure while syncing identities from gorm to bolt: %+v", err)
}
}
func (b *Broker) sessionDeleteEventHandler(args ...interface{}) {
var session *persistence.Session
if len(args) == 1 {
session, _ = args[0].(*persistence.Session)
}
if session == nil {
log := pfxlog.Logger()
log.Error("could not cast event args to event details")
return
}
b.sendSessionDeletes(session)
}
func (b *Broker) sendSessionDeletes(sessions ...*persistence.Session) {
removedByClusterId := map[string]*edge_ctrl_pb.SessionRemoved{}
for _, session := range sessions {
service, err := b.ae.Handlers.Service.HandleRead(session.ServiceId)
if err != nil {
log := pfxlog.Logger()
log.WithField("cause", err).Error("could not send network session removed, could not find service")
continue
}
for _, clusterId := range service.Clusters {
var ok bool
if _, ok = removedByClusterId[clusterId]; !ok {
removedByClusterId[clusterId] = &edge_ctrl_pb.SessionRemoved{}
}
removedByClusterId[clusterId].Tokens = append(removedByClusterId[clusterId].Tokens, session.Token)
}
}
for clusterId, nsr := range removedByClusterId {
entries := b.clusterMap.GetOnlineEntries(clusterId)
for _, e := range entries {
if buf, err := proto.Marshal(nsr); err == nil {
if err = e.Channel.Send(channel2.NewMessage(SessionRemovedType, buf)); err != nil {
pfxlog.Logger().WithField("cause", err).Error("error sending network session removed")
}
} else {
pfxlog.Logger().WithField("cause", err).Error("error sending network session removed, could not marshal message content")
}
}
}
}
func (b *Broker) sessionCreateEventHandler(args ...interface{}) {
var session *persistence.Session
if len(args) == 1 {
session, _ = args[0].(*persistence.Session)
}
if session == nil {
log := pfxlog.Logger()
log.Error("could not cast event args to event details")
return
}
b.sendSessionCreates(session)
}
func (b *Broker) sendSessionCreates(sessions ...*persistence.Session) {
addByClusterId := map[string]*edge_ctrl_pb.SessionAdded{}
for _, session := range sessions {
service, err := b.ae.Handlers.Service.HandleRead(session.ServiceId)
if err != nil {
log := pfxlog.Logger()
log.WithField("cause", err).Error("could not send network session added, could not find service")
continue
}
for _, clusterId := range service.Clusters {
var ok bool
if _, ok = addByClusterId[clusterId]; !ok {
addByClusterId[clusterId] = &edge_ctrl_pb.SessionAdded{}
}
fps, err := b.getActiveFingerprints(session.Id)
if err != nil {
pfxlog.Logger().Errorf("could not obtain a fingerprint for the api session [%s] and session [%s]", session.ApiSessionId, session.Id)
continue
}
svc, err := b.modelServiceToProto(service)
if err != nil {
pfxlog.Logger().Errorf("could not convert service [%s] to proto: %s", service.Id, err)
continue
}
addByClusterId[clusterId].Sessions = append(addByClusterId[clusterId].Sessions, &edge_ctrl_pb.Session{
Token: session.Token,
Service: svc,
CertFingerprints: fps,
Hosting: session.IsHosting,
})
}
}
for clusterId, nsr := range addByClusterId {
entries := b.clusterMap.GetOnlineEntries(clusterId)
for _, e := range entries {
if buf, err := proto.Marshal(nsr); err == nil {
if err = e.Channel.Send(channel2.NewMessage(SessionAddedType, buf)); err != nil {
pfxlog.Logger().WithField("cause", err).Error("error sending network session added")
}
} else {
pfxlog.Logger().WithField("cause", err).Error("error sending network session added, could not marshal message content")
}
}
}
}
func (b *Broker) modelServiceToProto(service *model.Service) (*edge_ctrl_pb.Service, error) {
return &edge_ctrl_pb.Service{
Name: service.Name,
Id: service.Id,
EndpointAddress: service.EndpointAddress,
EgressRouter: service.EgressRouter,
}, nil
}
func (b *Broker) registerEventHandlers() {
for s, enls := range b.events {
for en, ls := range enls {
for _, l := range ls {
s.AddListener(en, l)
}
}
}
}
func (b *Broker) getCurrentSessions() (*edge_ctrl_pb.ApiSessionAdded, error) {
ret := &edge_ctrl_pb.ApiSessionAdded{
IsFullState: true,
}
sessions, err := b.ae.GetHandlers().ApiSession.HandleQuery("true limit none")
if err != nil {
return nil, err
}
log := pfxlog.Logger()
for _, session := range sessions.ApiSessions {
sessionProto, err := b.modelApiSessionToProto(session.Token, session.IdentityId)
if err != nil {
log.Error(err)
continue
}
ret.ApiSessions = append(ret.ApiSessions, sessionProto)
}
return ret, nil
}
func (b *Broker) getCurrentStateNetworkSessions(clusterId string) (*edge_ctrl_pb.SessionAdded, error) {
ret := &edge_ctrl_pb.SessionAdded{
IsFullState: true,
}
query := fmt.Sprintf(`anyOf(service.clusters.id) = "%v" limit none`, clusterId)
result, err := b.ae.Handlers.Session.HandleQuery(query)
if err != nil {
return nil, err
}
log := pfxlog.Logger()
for _, session := range result.Sessions {
nu, err := b.modelSessionToProto(session)
if err != nil {
log.Error(err)
continue
}
ret.Sessions = append(ret.Sessions, nu)
}
return ret, nil
}
func (b *Broker) getActiveFingerprints(sessionId string) ([]string, error) {
certs, err := b.ae.Handlers.Session.HandleReadSessionCerts(sessionId)
if err != nil {
return nil, err
}
var ret []string
now := time.Now()
for _, c := range certs {
if now.After(c.ValidFrom) && now.Before(c.ValidTo) {
ret = append(ret, c.Fingerprint)
}
}
return ret, nil
}
func (b *Broker) getApiSessionFingerprints(identityId string) ([]string, error) {
fingerprintsMap := map[string]bool{}
err := b.ae.Handlers.Identity.HandleCollectAuthenticators(identityId, func(entity model.BaseModelEntity) error {
authenticator, ok := entity.(*model.Authenticator)
if !ok {
return fmt.Errorf("unexpected type %v when converting base model entity to authenticator", reflect.TypeOf(entity))
}
for _, authPrint := range authenticator.Fingerprints() {
fingerprintsMap[authPrint] = true
}
return nil
})
if err != nil {
return nil, err
}
var fingerprints []string
for fingerprint, _ := range fingerprintsMap {
fingerprints = append(fingerprints, fingerprint)
}
return fingerprints, nil
}
func (b *Broker) modelApiSessionToProto(token, identityId string) (*edge_ctrl_pb.ApiSession, error) {
fingerprints, err := b.getApiSessionFingerprints(identityId)
if err != nil {
return nil, err
}
return &edge_ctrl_pb.ApiSession{
Token: token,
CertFingerprints: fingerprints,
}, nil
}
func (b *Broker) modelSessionToProto(ns *model.Session) (*edge_ctrl_pb.Session, error) {
service, err := b.ae.Handlers.Service.HandleRead(ns.ServiceId)
if err != nil {
return nil, fmt.Errorf("could not convert to session proto, could not find service: %s", err)
}
apiSession, err := b.ae.Handlers.ApiSession.HandleRead(ns.ApiSessionId)
if err != nil {
return nil, fmt.Errorf("could not convert to network session proto, could not find session: %s", err)
}
fps, err := b.getActiveFingerprints(ns.Id)
if err != nil {
return nil, fmt.Errorf("could not get fingerprints for network session: %s", err)
}
svc, err := b.modelServiceToProto(service)
if err != nil {
return nil, fmt.Errorf("could not convert service [%s] to proto: %s", service.Id, err)
}
return &edge_ctrl_pb.Session{
Token: ns.Token,
SessionToken: apiSession.Token,
Service: svc,
CertFingerprints: fps,
Hosting: ns.IsHosting,
}, nil
}
func (b *Broker) GetOnlineEdgeRouter(id string) *model.EdgeRouter {
var edgeRouter *model.EdgeRouter
b.clusterMap.RangeEdgeRouterEntries(func(edgeRouterEntry *edgeRouterEntry) bool {
if edgeRouterEntry.EdgeRouter.GetId() == id {
edgeRouter = edgeRouterEntry.EdgeRouter
return false
}
return true
})
return edgeRouter
}
func (b *Broker) ClusterHasEdgeRouterOnline(clusterId string) bool {
entries := b.clusterMap.GetOnlineEntries(clusterId)
return len(entries) > 0
}
func (b *Broker) GetOnlineEdgeRoutersByCluster(clusterId string) []*model.EdgeRouter {
var ret []*model.EdgeRouter
for _, entry := range b.clusterMap.GetOnlineEntries(clusterId) {
ret = append(ret, entry.EdgeRouter)
}
return ret
}
func (b *Broker) RouterConnected(r *network.Router) {
go func() {
fp := formatFingerprint(r.Fingerprint)
edgeRouter, _ := b.ae.Handlers.EdgeRouter.HandleReadOneByFingerprint(fp)
// not an edge router
if edgeRouter == nil || edgeRouter.Id == "" {
return
}
b.sendHello(r, edgeRouter, fp)
}()
}
func (b *Broker) sendHello(r *network.Router, edgeRouter *model.EdgeRouter, fingerprint string) {
serverHello := &edge_ctrl_pb.ServerHello{
Version: version.GetVersion(),
}
if buf, err := proto.Marshal(serverHello); err == nil {
if waitCh, err := r.Control.SendAndWait(channel2.NewMessage(ServerHelloType, buf)); err == nil {
select {
case resp := <-waitCh:
if resp.ContentType == ClientHelloType {
respHello := &edge_ctrl_pb.ClientHello{}
if err := proto.Unmarshal(resp.Body, respHello); err == nil {
pfxlog.Logger().
WithField("version", respHello.Version).
WithField("hostname", respHello.Hostname).
WithField("protocols", respHello.Protocols).
WithField("data", respHello.Data).
Info("edge router responded with client hello")
protocols := map[string]string{}
for _, p := range respHello.Protocols {
ingressUrl := fmt.Sprintf("%s://%s", p, respHello.Hostname)
protocols[p] = ingressUrl
}
//in memory only
edgeRouter.Hostname = &respHello.Hostname
edgeRouter.EdgeRouterProtocols = protocols
//todo: restrict version?
pfxlog.Logger().Infof("edge router connecting with version [%s] to controller with version [%s]", respHello.Version, version.GetVersion())
b.AddEdgeRouter(r.Control, edgeRouter)
} else {
pfxlog.Logger().WithField("cause", err).Error("could not unmarshal clientHello after serverHello")
return
}
}
if resp.ContentType == ErrorType {
respErr := &edge_ctrl_pb.Error{}
if err := proto.Unmarshal(resp.Body, respErr); err == nil {
pfxlog.Logger().WithField("cause", respErr.Cause).WithField("code", respErr.Code).WithField("message", respErr.Message).
Error("client responded with error after serverHello")
return
} else {
pfxlog.Logger().WithField("cause", err).Error("could not unmarshal error from client after serverHello")
return
}
}
case <-time.After(5 * time.Second):
pfxlog.Logger().Error("timeout - waiting for clientHello from edge router")
}
} else {
pfxlog.Logger().WithField("cause", err).Error("could not send serverHello message for edge router")
return
}
}
}
func (b *Broker) RouterDisconnected(r *network.Router) {
go func() {
fp := formatFingerprint(r.Fingerprint)
edgeRouter, _ := b.ae.Handlers.EdgeRouter.HandleReadOneByFingerprint(fp)
// not an edge router
if edgeRouter == nil || edgeRouter.Id == "" {
return
}
b.clusterMap.RemoveEntry(edgeRouter.ClusterId, edgeRouter.Id)
}()
}
func formatFingerprint(fp string) string {
fp = strings.ToUpper(fp)
return insertNth(fp, 2, ":")
}
func insertNth(s string, n int, i string) string {
rs := []rune(i)
var buffer bytes.Buffer
var n_1 = n - 1
var l_1 = len(s) - 1
for i, cr := range s {
buffer.WriteRune(cr)
if i%n == n_1 && i != l_1 {
for _, r := range rs {
buffer.WriteRune(r)
}
}
}
return buffer.String()
}
+67
View File
@@ -0,0 +1,67 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
import (
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/migration"
"encoding/json"
"fmt"
"github.com/michaelquigley/pfxlog"
)
type DefaultEventLogger struct {
Ae *AppEnv
}
func (el *DefaultEventLogger) Log(actorType, actorId, eventType, entityType, entityId, formatString string, formatData []string, data map[interface{}]interface{}) {
fmtDataStr, err := json.Marshal(formatData)
if err != nil {
pfxlog.Logger().WithField("cause", err).Error("could not save event log")
return
}
var fmtInt []interface{}
for _, fd := range formatData {
fmtInt = append(fmtInt, fd)
}
pm := migration.PropertyMap{}
for k, v := range data {
ks := k.(string)
pm[ks] = v
}
_, err = el.Ae.Handlers.EventLog.HandleCreate(&model.EventLog{
Type: eventType,
ActorId: actorId,
ActorType: actorType,
EntityId: entityId,
EntityType: entityType,
FormatData: string(fmtDataStr),
FormatString: formatString,
FormattedMessage: fmt.Sprintf(formatString, fmtInt...),
Data: pm,
})
if err != nil {
pfxlog.Logger().WithField("cause", err).Errorf("could not store event log: %s", err)
}
}
+56
View File
@@ -0,0 +1,56 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
import (
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/netfoundry/ziti-edge/edge/migration"
"time"
)
type BaseApi struct {
Id string `json:"id"`
CreatedAt *time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt"`
Links *response.Links `json:"_links"`
Tags map[string]interface{} `json:"tags"`
}
func FromBaseDbEntity(entity *migration.BaseDbEntity) *BaseApi {
var tags map[string]interface{}
if entity.Tags != nil {
tags = *entity.Tags
}
return &BaseApi{
Id: entity.ID,
UpdatedAt: entity.UpdatedAt,
CreatedAt: entity.CreatedAt,
Tags: tags,
}
}
func FromBaseModelEntity(entity model.BaseModelEntity) *BaseApi {
createdAt := entity.GetCreatedAt()
updatedAt := entity.GetUpdatedAt()
return &BaseApi{
Id: entity.GetId(),
UpdatedAt: &updatedAt,
CreatedAt: &createdAt,
Tags: entity.GetTags(),
}
}
+184
View File
@@ -0,0 +1,184 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
import (
"github.com/netfoundry/ziti-edge/edge/controller/config"
"github.com/netfoundry/ziti-edge/edge/controller/packrsource"
"github.com/netfoundry/ziti-edge/edge/migration/certOLD"
"github.com/netfoundry/ziti-edge/edge/migration/updbOLD"
"database/sql"
"fmt"
"github.com/golang-migrate/migrate"
"github.com/golang-migrate/migrate/database"
"github.com/golang-migrate/migrate/database/postgres"
"github.com/golang-migrate/migrate/source"
_ "github.com/golang-migrate/migrate/source/file"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/michaelquigley/pfxlog"
"net/url"
)
const PackrPrefix = "embedded"
const MigrationPathPostgres = "embedded://db/postgres/migrations"
type DriverGenerator interface {
AppDriver(cs string) (*gorm.DB, error)
MigrationDriver(cs string) (database.Driver, error)
Name() string
}
type PostgresDriverGenerator struct {
}
func (PostgresDriverGenerator) Name() string {
return "postgres"
}
func (pg *PostgresDriverGenerator) AppDriver(cs string) (*gorm.DB, error) {
appCs, err := pg.appString(cs)
if err != nil {
return nil, err
}
return gorm.Open("postgres", appCs)
}
func (pg *PostgresDriverGenerator) MigrationDriver(cs string) (database.Driver, error) {
migCs := pg.migrationString(cs)
s, err := sql.Open("postgres", migCs)
if err != nil {
pfxlog.Logger().WithField("cause", err).Panic("error during sql.Open")
}
return postgres.WithInstance(s, &postgres.Config{
MigrationsTable: "ziti_edge_version",
})
}
func (pg *PostgresDriverGenerator) appString(s string) (string, error) {
u, err := url.Parse(s)
if err != nil {
return "", fmt.Errorf("connection string must be a well formatted URL: %s", err)
}
q := u.Query()
q.Set("search_path", "ziti_edge,public")
u.RawQuery = q.Encode()
return u.String(), nil
}
func (pg *PostgresDriverGenerator) migrationString(s string) string {
return s
}
func InitPersistence(p *config.Persistence) *gorm.DB {
if p == nil || p.Postgres.ConnectionUrl == "" {
return nil
}
for _, migration := range certOLD.GetCertMigrations() {
p.AddMigrationBox(migration)
}
for _, migration := range updbOLD.GetUpdbMigrations() {
p.AddMigrationBox(migration)
}
source.Register(PackrPrefix, packrsource.NewPakrSource(p.GetBoxes()...))
log := pfxlog.Logger()
var dg DriverGenerator
var cs string
switch {
case p.Postgres != nil:
dg = &PostgresDriverGenerator{}
cs = p.Postgres.ConnectionUrl
default:
log.Fatal("no supported persistence configuration defined")
}
md, err := dg.MigrationDriver(cs)
if err != nil {
log.Fatalf("failed to connect to data store (%s) to check schema version: %+v", dg.Name(), err)
}
defer func() {
err := md.Close()
if err != nil {
log.Fatal(err)
}
}()
ad, err := dg.AppDriver(cs)
ad.LogMode(true)
ad.SetLogger(&logrusLogger{})
if err != nil {
log.Fatal(err)
}
migrateDb(p, md)
return ad
}
func migrateDb(p *config.Persistence, drv database.Driver) {
log := pfxlog.Logger()
m, err := migrate.NewWithDatabaseInstance(MigrationPathPostgres, p.Postgres.DbName, drv)
if err != nil {
log.Fatal("could not configure migration client: ", err)
}
log.Info("checking database schema version...")
defer func() {
err := recover()
if err != nil {
log.Fatalf("Failure while migrating database: %+v", err)
}
}()
err = m.Up()
if err != nil {
if err != migrate.ErrNoChange {
log.Fatal("...could not migrate database: ", err)
}
log.Info("...no database upgrade required")
} else {
log.Info("...database upgraded")
}
}
type logrusLogger struct{}
func (logrusLogger) Print(v ...interface{}) {
pfxlog.Logger().WithField("type", "sql").Trace(v...)
}
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 env
var routerFuncs []AddRouterFunc
type AddRouterFunc func(ae *AppEnv)
type ApiRouter interface {
Register(ae *AppEnv)
}
func AddRouter(rf ApiRouter) {
routerFuncs = append(routerFuncs, rf.Register)
}
func GetRouters() []AddRouterFunc {
return routerFuncs
}
@@ -0,0 +1,58 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_edge_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/pb/edge_ctrl_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type sessionHeartbeatHandler struct {
appEnv *env.AppEnv
}
func NewSessionHeartbeatHandler(appEnv *env.AppEnv) *sessionHeartbeatHandler {
return &sessionHeartbeatHandler{
appEnv: appEnv,
}
}
func (h *sessionHeartbeatHandler) ContentType() int32 {
return env.ApiSessionHeartbeatType
}
func (h *sessionHeartbeatHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
go func() {
req := &edge_ctrl_pb.ApiSessionHeartbeat{}
if err := proto.Unmarshal(msg.Body, req); err == nil {
err := h.appEnv.Stores.Session.MarkActivity(req.Tokens)
if err != nil {
pfxlog.Logger().
WithError(err).
Error("unable to set activity for heartbeat")
}
} else {
pfxlog.Logger().Panic("could not convert message as session heartbeat")
}
}()
}
@@ -0,0 +1,29 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 permissions
type AlwaysAllow struct{}
var always = &AlwaysAllow{}
func (a *AlwaysAllow) IsAllowed(_ ...string) bool {
return true
}
func Always() *AlwaysAllow {
return always
}
@@ -0,0 +1,35 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 permissions
type RequireAdmin struct{}
var requireAdmin = &RequireAdmin{}
func IsAdmin() *RequireAdmin {
return requireAdmin
}
func (ia *RequireAdmin) IsAllowed(identityPerms ...string) bool {
for _, p := range identityPerms {
if p == AdminPermission {
return true
}
}
return false
}
@@ -0,0 +1,35 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 permissions
type RequireAuthenticated struct{}
var isAuthenticated = &RequireAuthenticated{}
func IsAuthenticated() *RequireAuthenticated {
return isAuthenticated
}
func (ir *RequireAuthenticated) IsAllowed(identityPerms ...string) bool {
for _, p := range identityPerms {
if p == AuthenticatedPermission {
return true
}
}
return false
}
@@ -0,0 +1,26 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 permissions
const (
AdminPermission = "ADMIN"
AuthenticatedPermission = "AUTHENTICATED"
)
type Resolver interface {
IsAllowed(...string) bool
}
@@ -0,0 +1,52 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 permissions
type RequireAll struct {
required []string
}
func NewRequireAll(perms ...string) *RequireAll {
ra := &RequireAll{
required: perms,
}
return ra
}
func (ra *RequireAll) IsAllowed(identityPerms ...string) bool {
ps := map[string]bool{}
if len(identityPerms) < len(ra.required) {
return false
}
for _, p := range identityPerms {
ps[p] = true
//short cut admins
if p == AdminPermission {
return true
}
}
for _, r := range ra.required {
if _, ok := ps[r]; !ok {
return false
}
}
return true
}
@@ -0,0 +1,47 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 permissions
type RequireOne struct {
required map[string]bool
}
func NewRequireOne(perms ...string) *RequireOne {
ro := &RequireOne{}
ro.required = map[string]bool{}
for _, p := range perms {
ro.required[p] = true
}
return ro
}
func (ro *RequireOne) IsAllowed(identityPerms ...string) bool {
for _, p := range identityPerms {
//short cut admins
if p == AdminPermission {
return true
}
if _, ok := ro.required[p]; ok {
return true
}
}
return false
}
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 policy
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/persistence"
"github.com/netfoundry/ziti-edge/edge/runner"
"fmt"
"go.etcd.io/bbolt"
"time"
)
type AppWanEnforcer struct {
appEnv *env.AppEnv
*runner.BaseOperation
}
func NewAppWanEnforcer(appEnv *env.AppEnv, f time.Duration) *AppWanEnforcer {
return &AppWanEnforcer{
appEnv: appEnv,
BaseOperation: runner.NewBaseOperation("AppWanEnforcer", f)}
}
func (enforcer *AppWanEnforcer) Run() error {
result, err := enforcer.appEnv.GetHandlers().Session.HandleQuery("")
if err != nil {
return err
}
service := &persistence.EdgeService{}
var sessionsToRemove []string
err = enforcer.appEnv.GetDbProvider().GetDb().View(func(tx *bbolt.Tx) error {
for _, session := range result.Sessions {
apiSession := &persistence.ApiSession{}
_, err := enforcer.appEnv.GetStores().ApiSession.BaseLoadOneById(tx, session.ApiSessionId, apiSession)
if err != nil {
return err
}
identity := &persistence.Identity{}
_, err = enforcer.appEnv.GetStores().Identity.BaseLoadOneById(tx, apiSession.IdentityId, identity)
if err != nil {
return err
}
if identity.IsAdmin == true {
continue
}
query := fmt.Sprintf(`id = "%v" and anyOf(appwans.identities.id) = "%v"`, session.ServiceId, apiSession.IdentityId)
found, err := enforcer.appEnv.GetStores().EdgeService.BaseLoadOneByQuery(tx, query, service)
if err != nil {
return err
}
if !found {
sessionsToRemove = append(sessionsToRemove, session.Id)
}
}
return nil
})
if err != nil {
return err
}
for _, sessionId := range sessionsToRemove {
_ = enforcer.appEnv.GetHandlers().Session.HandleDelete(sessionId)
}
return nil
}
@@ -0,0 +1,64 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 policy
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/runner"
"fmt"
"github.com/michaelquigley/pfxlog"
"time"
)
type SessionEnforcer struct {
appEnv *env.AppEnv
sessionTimeout time.Duration
*runner.BaseOperation
}
func NewSessionEnforcer(appEnv *env.AppEnv, frequency time.Duration, sessionTimeout time.Duration) *SessionEnforcer {
if sessionTimeout < 60*time.Second {
pfxlog.Logger().Panic("sessionTimeout can not be less than 60 seconds")
}
pfxlog.Logger().
WithField("sessionTimeout", sessionTimeout.String()).
WithField("frequency", frequency.String()).
Info("session enforcer configured")
return &SessionEnforcer{
appEnv: appEnv,
sessionTimeout: sessionTimeout,
BaseOperation: runner.NewBaseOperation("SessionEnforcer", frequency),
}
}
func (s *SessionEnforcer) Run() error {
oldest := time.Now().Add(s.sessionTimeout * -1)
query := fmt.Sprintf("updatedAt < datetime(%s)", oldest.UTC().Format(time.RFC3339))
result, err := s.appEnv.GetHandlers().ApiSession.HandleQuery(query)
if err != nil {
return err
}
for _, apiSession := range result.ApiSessions {
_ = s.appEnv.GetHandlers().ApiSession.HandleDelete(apiSession.Id)
}
return nil
}
@@ -0,0 +1,109 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"fmt"
"github.com/michaelquigley/pfxlog"
)
const EntityNameSession = "sessions"
func NewApiSessionEntityRef(s *model.ApiSession) *EntityApiRef {
links := &response.Links{
"self": NewApiSessionLink(s.Id),
}
return &EntityApiRef{
Id: s.Id,
Name: nil,
Links: links,
}
}
func NewApiSessionLink(sessionId string) *response.Link {
return response.NewLink(fmt.Sprintf("./%s/%s", EntityNameSession, sessionId))
}
type ApiSessionApiList struct {
*env.BaseApi
Token *string `json:"token"`
Identity *EntityApiRef `json:"identity"`
}
func (ApiSessionApiList) BuildSelfLink(id string) *response.Link {
return NewApiSessionLink(id)
}
func (e *ApiSessionApiList) GetSelfLink() *response.Link {
return e.BuildSelfLink(e.Id)
}
func (e *ApiSessionApiList) PopulateLinks() {
if e.Links == nil {
e.Links = &response.Links{
EntityNameSelf: e.GetSelfLink(),
}
}
}
func (e *ApiSessionApiList) ToEntityApiRef() *EntityApiRef {
e.PopulateLinks()
return &EntityApiRef{
Entity: EntityNameSession,
Name: nil,
Id: e.Id,
Links: e.Links,
}
}
func MapApiSessionToApiEntity(_ *env.AppEnv, _ *response.RequestContext, e model.BaseModelEntity) (BaseApiEntity, error) {
i, ok := e.(*model.ApiSession)
if !ok {
err := fmt.Errorf("entity is not an ApiSession \"%s\"", e.GetId())
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
al, err := MapApiSessionToApiList(i)
if err != nil {
err := fmt.Errorf("could not convert to API entity \"%s\": %s", e.GetId(), err)
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
return al, nil
}
func MapApiSessionToApiList(i *model.ApiSession) (*ApiSessionApiList, error) {
ret := &ApiSessionApiList{
BaseApi: env.FromBaseModelEntity(i),
Token: &i.Token,
Identity: NewIdentityEntityRef(i.Identity),
}
ret.PopulateLinks()
return ret, nil
}
@@ -0,0 +1,56 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/internal/permissions"
"github.com/netfoundry/ziti-edge/edge/controller/response"
)
func init() {
r := NewApiSessionRouter()
env.AddRouter(r)
}
type ApiSessionRouter struct {
BasePath string
IdType response.IdType
}
func NewApiSessionRouter() *ApiSessionRouter {
return &ApiSessionRouter{
BasePath: "/" + EntityNameSession,
IdType: response.IdTypeUuid,
}
}
func (ir *ApiSessionRouter) Register(ae *env.AppEnv) {
registerReadDeleteOnlyRouter(ae, ae.RootRouter, ir.BasePath, ir, permissions.IsAdmin())
}
func (ir *ApiSessionRouter) List(ae *env.AppEnv, rc *response.RequestContext) {
ListWithHandler(ae, rc, ae.Handlers.ApiSession, MapApiSessionToApiEntity)
}
func (ir *ApiSessionRouter) Detail(ae *env.AppEnv, rc *response.RequestContext) {
DetailWithHandler(ae, rc, ae.Handlers.ApiSession, MapApiSessionToApiEntity, ir.IdType)
}
func (ir *ApiSessionRouter) Delete(ae *env.AppEnv, rc *response.RequestContext) {
DeleteWithHandler(rc, ir.IdType, ae.Handlers.ApiSession)
}
@@ -0,0 +1,141 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/util/stringz"
)
const EntityNameAppWan = "app-wans"
type AppWanApiUpdate struct {
Tags *map[string]interface{} `json:"tags"`
Name *string `json:"name"`
}
func (i *AppWanApiUpdate) ToModel(id string) *model.Appwan {
result := &model.Appwan{}
result.Id = id
result.Name = stringz.OrEmpty(i.Name)
if i.Tags != nil {
result.Tags = *i.Tags
}
return result
}
type AppWanApiCreate struct {
Name *string `json:"name"`
Identities []string `json:"identities"`
Services []string `json:"services"`
Tags *map[string]interface{} `json:"tags"`
}
func NewAppWanApiCreate() *AppWanApiCreate {
return &AppWanApiCreate{
Identities: []string{},
Services: []string{},
}
}
func (i *AppWanApiCreate) ToModel() *model.Appwan {
result := &model.Appwan{}
result.Name = stringz.OrEmpty(i.Name)
if i.Tags != nil {
result.Tags = *i.Tags
}
result.Identities = i.Identities
result.Services = i.Services
return result
}
type AppWanApiList struct {
*env.BaseApi
Name *string `json:"name"`
}
func (AppWanApiList) BuildSelfLink(id string) *response.Link {
return response.NewLink(fmt.Sprintf("./%s/%s", EntityNameAppWan, id))
}
func (e *AppWanApiList) GetSelfLink() *response.Link {
return e.BuildSelfLink(e.Id)
}
func (e *AppWanApiList) GetIdentitiesLink() *response.Link {
return response.NewLink(fmt.Sprintf("./%s/%s/%s", EntityNameAppWan, e.Id, EntityNameIdentity))
}
func (e *AppWanApiList) GetServicesLink() *response.Link {
return response.NewLink(fmt.Sprintf("./%s/%s/%s", EntityNameAppWan, e.Id, EntityNameService))
}
func (e *AppWanApiList) PopulateLinks() {
if e.Links == nil {
e.Links = &response.Links{
EntityNameSelf: e.GetSelfLink(),
EntityNameService: e.GetServicesLink(),
EntityNameIdentity: e.GetIdentitiesLink(),
}
}
}
func (e *AppWanApiList) ToEntityApiRef() *EntityApiRef {
e.PopulateLinks()
return &EntityApiRef{
Entity: EntityNameAppWan,
Name: e.Name,
Id: e.Id,
Links: e.Links,
}
}
func MapAppWanToApiEntity(_ *env.AppEnv, _ *response.RequestContext, e model.BaseModelEntity) (BaseApiEntity, error) {
i, ok := e.(*model.Appwan)
if !ok {
err := fmt.Errorf("entity is not an appwan \"%s\"", e.GetId())
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
al, err := MapAppWanToApiList(i)
if err != nil {
err := fmt.Errorf("could not convert to API entity \"%s\": %s", e.GetId(), err)
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
return al, nil
}
func MapAppWanToApiList(i *model.Appwan) (*AppWanApiList, error) {
ret := &AppWanApiList{
BaseApi: env.FromBaseModelEntity(i),
Name: &i.Name,
}
ret.PopulateLinks()
return ret, nil
}
@@ -0,0 +1,178 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/internal/permissions"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/persistence"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"fmt"
"net/http"
)
func init() {
r := NewAppWanRouter()
env.AddRouter(r)
}
type AppWanRouter struct {
BasePath string
IdType response.IdType
}
func NewAppWanRouter() *AppWanRouter {
return &AppWanRouter{
BasePath: "/" + EntityNameAppWan,
IdType: response.IdTypeUuid,
}
}
func (ir *AppWanRouter) Register(ae *env.AppEnv) {
sr := registerCrudRouter(ae, ae.RootRouter, ir.BasePath, ir, permissions.IsAdmin())
serviceUrlWithSlash := fmt.Sprintf("/{%s}/services", response.IdPropertyName)
serviceUrlWithOutSlash := fmt.Sprintf("/{%s}/services/", response.IdPropertyName)
servicesListHandler := ae.WrapHandler(ir.ListServices, permissions.IsAdmin())
servicesAddHandler := ae.WrapHandler(ir.AddServices, permissions.IsAdmin())
servicesRemoveHandler := ae.WrapHandler(ir.RemoveServicesBulk, permissions.IsAdmin())
servicesSetHandler := ae.WrapHandler(ir.SetServices, permissions.IsAdmin())
sr.HandleFunc(serviceUrlWithSlash, servicesListHandler).Methods(http.MethodGet)
sr.HandleFunc(serviceUrlWithOutSlash, servicesListHandler).Methods(http.MethodGet)
sr.HandleFunc(serviceUrlWithSlash, servicesAddHandler).Methods(http.MethodPut)
sr.HandleFunc(serviceUrlWithOutSlash, servicesAddHandler).Methods(http.MethodPut)
sr.HandleFunc(serviceUrlWithSlash, servicesRemoveHandler).Methods(http.MethodDelete)
sr.HandleFunc(serviceUrlWithOutSlash, servicesRemoveHandler).Methods(http.MethodDelete)
sr.HandleFunc(serviceUrlWithSlash, servicesSetHandler).Methods(http.MethodPost)
sr.HandleFunc(serviceUrlWithOutSlash, servicesSetHandler).Methods(http.MethodPost)
identityUrlWithSlash := fmt.Sprintf("/{%s}/identities", response.IdPropertyName)
identityUrlWithOutSlash := fmt.Sprintf("/{%s}/identities/", response.IdPropertyName)
identitiesListHandler := ae.WrapHandler(ir.ListIdentities, permissions.IsAdmin())
identitiesAddHandler := ae.WrapHandler(ir.AddIdentities, permissions.IsAdmin())
identitiesBulkRemoveHandler := ae.WrapHandler(ir.RemoveIdentitiesBulk, permissions.IsAdmin())
identitiesSetHandler := ae.WrapHandler(ir.SetIdentities, permissions.IsAdmin())
sr.HandleFunc(identityUrlWithSlash, identitiesListHandler).Methods(http.MethodGet)
sr.HandleFunc(identityUrlWithOutSlash, identitiesListHandler).Methods(http.MethodGet)
sr.HandleFunc(identityUrlWithSlash, identitiesAddHandler).Methods(http.MethodPut)
sr.HandleFunc(identityUrlWithOutSlash, identitiesAddHandler).Methods(http.MethodPut)
sr.HandleFunc(identityUrlWithSlash, identitiesBulkRemoveHandler).Methods(http.MethodDelete)
sr.HandleFunc(identityUrlWithOutSlash, identitiesBulkRemoveHandler).Methods(http.MethodDelete)
sr.HandleFunc(identityUrlWithSlash, identitiesSetHandler).Methods(http.MethodPost)
sr.HandleFunc(identityUrlWithOutSlash, identitiesSetHandler).Methods(http.MethodPost)
identityUrlWithSlashWithSubId := fmt.Sprintf("/{%s}/identities/{%s}", response.IdPropertyName, response.SubIdPropertyName)
identityUrlWithOutSlashWithSubId := fmt.Sprintf("/{%s}/identities/{%s}/", response.IdPropertyName, response.SubIdPropertyName)
identityRemoveHandler := ae.WrapHandler(ir.RemoveIdentity, permissions.IsAdmin())
sr.HandleFunc(identityUrlWithSlashWithSubId, identityRemoveHandler).Methods(http.MethodDelete)
sr.HandleFunc(identityUrlWithOutSlashWithSubId, identityRemoveHandler).Methods(http.MethodDelete)
serviceUrlWithSlashWithSubId := fmt.Sprintf("/{%s}/services/{%s}", response.IdPropertyName, response.SubIdPropertyName)
serviceUrlWithOutSlashWithSubId := fmt.Sprintf("/{%s}/services/{%s}/", response.IdPropertyName, response.SubIdPropertyName)
serviceRemoveHandler := ae.WrapHandler(ir.RemoveService, permissions.IsAdmin())
sr.HandleFunc(serviceUrlWithSlashWithSubId, serviceRemoveHandler).Methods(http.MethodDelete)
sr.HandleFunc(serviceUrlWithOutSlashWithSubId, serviceRemoveHandler).Methods(http.MethodDelete)
}
func (ir *AppWanRouter) List(ae *env.AppEnv, rc *response.RequestContext) {
ListWithHandler(ae, rc, ae.Handlers.Appwan, MapAppWanToApiEntity)
}
func (ir *AppWanRouter) Detail(ae *env.AppEnv, rc *response.RequestContext) {
DetailWithHandler(ae, rc, ae.Handlers.Appwan, MapAppWanToApiEntity, ir.IdType)
}
func (ir *AppWanRouter) Create(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := NewAppWanApiCreate()
Create(rc, rc.RequestResponder, ae.Schemes.AppWan.Post, apiEntity, (&AppWanApiList{}).BuildSelfLink, func() (string, error) {
return ae.Handlers.Appwan.HandleCreate(apiEntity.ToModel())
})
}
func (ir *AppWanRouter) Delete(ae *env.AppEnv, rc *response.RequestContext) {
DeleteWithHandler(rc, ir.IdType, ae.Handlers.Appwan)
}
func (ir *AppWanRouter) Update(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &AppWanApiUpdate{}
Update(rc, ae.Schemes.AppWan.Put, ir.IdType, apiEntity, func(id string) error {
return ae.Handlers.Appwan.HandleUpdate(apiEntity.ToModel(id))
})
}
func (ir *AppWanRouter) Patch(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &AppWanApiUpdate{}
Patch(rc, ae.Schemes.AppWan.Patch, ir.IdType, apiEntity, func(id string, fields JsonFields) error {
return ae.Handlers.Appwan.HandlePatch(apiEntity.ToModel(id), fields.ConcatNestedNames())
})
}
func (ir *AppWanRouter) ListServices(ae *env.AppEnv, rc *response.RequestContext) {
ListAssociations(ae, rc, ir.IdType, ae.Handlers.Appwan.HandleCollectServices, MapServiceToApiEntity)
}
func (ir *AppWanRouter) AddServices(ae *env.AppEnv, rc *response.RequestContext) {
UpdateAssociationsFor(ae, rc, ir.IdType, ae.GetStores().Appwan, model.AssociationsActionAdd, persistence.FieldAppwanServices)
}
func (ir *AppWanRouter) RemoveServicesBulk(ae *env.AppEnv, rc *response.RequestContext) {
UpdateAssociationsFor(ae, rc, ir.IdType, ae.GetStores().Appwan, model.AssociationsActionRemove, persistence.FieldAppwanServices)
}
func (ir *AppWanRouter) RemoveService(ae *env.AppEnv, rc *response.RequestContext) {
RemoveAssociationFor(ae, rc, ir.IdType, ae.GetStores().Appwan, persistence.FieldAppwanServices)
}
func (ir *AppWanRouter) SetServices(ae *env.AppEnv, rc *response.RequestContext) {
UpdateAssociationsFor(ae, rc, ir.IdType, ae.GetStores().Appwan, model.AssociationsActionSet, persistence.FieldAppwanServices)
}
func (ir *AppWanRouter) ListIdentities(ae *env.AppEnv, rc *response.RequestContext) {
ListAssociations(ae, rc, ir.IdType, ae.Handlers.Appwan.HandleCollectIdentities, MapIdentityToApiEntity)
}
func (ir *AppWanRouter) AddIdentities(ae *env.AppEnv, rc *response.RequestContext) {
UpdateAssociationsFor(ae, rc, ir.IdType, ae.BoltStores.Appwan, model.AssociationsActionAdd, persistence.FieldAppwanIdentities)
}
func (ir *AppWanRouter) RemoveIdentitiesBulk(ae *env.AppEnv, rc *response.RequestContext) {
UpdateAssociationsFor(ae, rc, ir.IdType, ae.BoltStores.Appwan, model.AssociationsActionRemove, persistence.FieldAppwanIdentities)
}
func (ir *AppWanRouter) RemoveIdentity(ae *env.AppEnv, rc *response.RequestContext) {
RemoveAssociationFor(ae, rc, ir.IdType, ae.BoltStores.Appwan, persistence.FieldAppwanIdentities)
}
func (ir *AppWanRouter) SetIdentities(ae *env.AppEnv, rc *response.RequestContext) {
UpdateAssociationsFor(ae, rc, ir.IdType, ae.BoltStores.Appwan, model.AssociationsActionSet, persistence.FieldAppwanIdentities)
}
@@ -0,0 +1,104 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/internal/permissions"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/google/uuid"
"github.com/michaelquigley/pfxlog"
"net/http"
"time"
)
func init() {
r := NewAuthRouter()
env.AddRouter(r)
}
type AuthRouter struct {
}
func NewAuthRouter() *AuthRouter {
return &AuthRouter{}
}
func (ro *AuthRouter) Register(ae *env.AppEnv) {
authHandler := ae.WrapHandler(ro.authHandler, permissions.Always())
ae.RootRouter.HandleFunc("/authenticate", authHandler).Methods("POST")
ae.RootRouter.HandleFunc("/authenticate/", authHandler).Methods("POST")
}
func (ro *AuthRouter) authHandler(ae *env.AppEnv, rc *response.RequestContext) {
authContext := &model.AuthContextHttp{}
err := authContext.FillFromHttpRequest(rc.Request)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
identity, err := ae.Handlers.Authenticator.HandleIsAuthorized(authContext)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
if identity == nil {
rc.RequestResponder.RespondWithUnauthorizedError(rc)
return
}
token := uuid.New().String()
s := &model.ApiSession{
IdentityId: identity.Id,
Token: token,
}
sessionId, err := ae.Handlers.ApiSession.HandleCreate(s)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
session, err := ae.Handlers.ApiSession.HandleRead(sessionId)
if err != nil {
pfxlog.Logger().WithField("cause", err).Error("loading session by id resulted in an error")
rc.RequestResponder.RespondWithUnauthorizedError(rc)
}
currentSession, err := RenderCurrentSessionApiListEntity(session, ae.Config.SessionTimeoutDuration())
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
expiration := time.Now().Add(365 * 24 * time.Hour)
cookie := http.Cookie{Name: ae.AuthCookieName, Value: token, Expires: expiration}
rc.ResponseWriter.Header().Set(ae.AuthHeaderName, session.Token)
http.SetCookie(rc.ResponseWriter, &cookie)
rc.RequestResponder.RespondWithOk(currentSession, nil)
}
@@ -0,0 +1,602 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/apierror"
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/persistence"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/netfoundry/ziti-edge/edge/controller/util"
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/michaelquigley/pfxlog"
"github.com/xeipuuv/gojsonschema"
"io/ioutil"
"strings"
)
const (
EntityNameSelf = "self"
)
type associationIdArrayRequest struct {
Ids []string `json:"ids"`
}
func unmarshal(body []byte, in interface{}) error {
err := json.Unmarshal(body, in)
if err != nil {
err = apierror.GetJsonParseError(err, body)
}
return err
}
type JsonFields map[string]bool
func (j JsonFields) IsUpdated(key string) bool {
_, ok := j[key]
return ok
}
func (j JsonFields) ConcatNestedNames() JsonFields {
for key, val := range j {
if strings.Contains(key, ".") {
delete(j, key)
key = strings.ReplaceAll(key, ".", "")
j[key] = val
}
}
return j
}
func getFields(body []byte) (JsonFields, error) {
jsonMap := map[string]interface{}{}
err := json.Unmarshal(body, &jsonMap)
if err != nil {
return nil, apierror.GetJsonParseError(err, body)
}
resultMap := JsonFields{}
getJsonFields("", jsonMap, resultMap)
return resultMap, nil
}
func getJsonFields(prefix string, m map[string]interface{}, result JsonFields) {
for k, v := range m {
name := strings.Title(k)
if subMap, ok := v.(map[string]interface{}); ok {
getJsonFields(prefix+name+".", subMap, result)
} else {
isSet := v != nil
result[prefix+name] = isSet
}
}
}
func modelToApi(ae *env.AppEnv, rc *response.RequestContext, mapper ModelToApiMapper, es []model.BaseModelEntity) ([]BaseApiEntity, error) {
apiEntities := make([]BaseApiEntity, 0)
for _, e := range es {
al, err := mapper(ae, rc, e)
if err != nil {
return nil, err
}
apiEntities = append(apiEntities, al)
}
return apiEntities, nil
}
func ListWithHandler(ae *env.AppEnv, rc *response.RequestContext, handler model.Handler, mapper ModelToApiMapper) {
List(rc, func(rc *response.RequestContext, queryOptions *model.QueryOptions) (*QueryResult, error) {
result, err := handler.BaseList(queryOptions)
if err != nil {
return nil, err
}
apiEntities, err := modelToApi(ae, rc, mapper, result.Entities)
if err != nil {
return nil, err
}
return NewQueryResult(apiEntities, &result.QueryMetaData), nil
})
}
type ModelListF func(rc *response.RequestContext, queryOptions *model.QueryOptions) (*QueryResult, error)
func List(rc *response.RequestContext, f ModelListF) {
qo, err := GetModelQueryOptionsFromRequest(rc.Request)
if err != nil {
log := pfxlog.Logger()
log.WithField("cause", err).Error("could not build query options")
rc.RequestResponder.RespondWithError(err)
return
}
result, err := f(rc, qo)
if err != nil {
log := pfxlog.Logger()
log.WithField("cause", err).Error("could not convert list")
rc.RequestResponder.RespondWithError(err)
return
}
if result.Result == nil {
result.Result = []BaseApiEntity{}
}
meta := &response.Meta{
"pagination": map[string]interface{}{
"limit": result.Limit,
"offset": result.Offset,
"totalCount": result.Count,
},
"filterableFields": result.FilterableFields,
}
rc.RequestResponder.RespondWithOk(result.Result, meta)
}
type ModelCreateF func() (string, error)
func Create(rc *response.RequestContext, rr response.RequestResponder, sc *gojsonschema.Schema, in interface{}, lb LinkBuilder, creator ModelCreateF) {
var body []byte
var err error
if body, err = ioutil.ReadAll(rc.Request.Body); err != nil {
rr.RespondWithCouldNotReadBody(err)
return
}
if err = unmarshal(body, in); err != nil {
rr.RespondWithCouldNotParseBody(err)
return
}
il := gojsonschema.NewGoLoader(in)
result, err := sc.Validate(il)
if err != nil {
rr.RespondWithError(err)
return
}
if !result.Valid() {
var errs []*apierror.ValidationError
for _, re := range result.Errors() {
errs = append(errs, apierror.NewValidationError(re))
}
rr.RespondWithValidationErrors(errs)
return
}
id, err := creator()
if err != nil {
if util.IsErrNotFoundErr(err) {
rr.RespondWithNotFound()
return
}
if fe, ok := err.(*model.FieldError); ok {
rr.RespondWithFieldError(fe)
return
}
rr.RespondWithError(err)
return
}
rr.RespondWithCreatedId(id, lb(id))
}
func DetailWithHandler(ae *env.AppEnv, rc *response.RequestContext, handler model.Handler, mapper ModelToApiMapper, idType response.IdType) {
Detail(rc, idType, func(rc *response.RequestContext, id string) (BaseApiEntity, error) {
entity, err := handler.BaseLoad(id)
if err != nil {
return nil, err
}
return mapper(ae, rc, entity)
})
}
type ModelDetailF func(rc *response.RequestContext, id string) (BaseApiEntity, error)
func Detail(rc *response.RequestContext, idType response.IdType, f ModelDetailF) {
id, err := rc.GetIdFromRequest(idType)
if err != nil {
pfxlog.Logger().Error(err)
rc.RequestResponder.RespondWithError(err)
return
}
apiEntity, err := f(rc, id)
if err != nil {
if util.IsErrNotFoundErr(err) {
rc.RequestResponder.RespondWithNotFound()
return
}
pfxlog.Logger().WithField("id", id).WithError(err).Error("could not load entity by id")
rc.RequestResponder.RespondWithError(err)
return
}
rc.RequestResponder.RespondWithOk(apiEntity, nil)
}
type ModelDeleteF func(rc *response.RequestContext, id string) error
type DeleteHandler interface {
HandleDelete(id string) error
}
func DeleteWithHandler(rc *response.RequestContext, idType response.IdType, deleteHandler DeleteHandler) {
Delete(rc, idType, func(rc *response.RequestContext, id string) error {
return deleteHandler.HandleDelete(id)
})
}
func Delete(rc *response.RequestContext, idType response.IdType, deleteF ModelDeleteF) {
id, err := rc.GetIdFromRequest(idType)
if err != nil {
log := pfxlog.Logger()
log.Error(err)
rc.RequestResponder.RespondWithError(err)
return
}
err = deleteF(rc, id)
if err != nil {
if util.IsErrNotFoundErr(err) {
rc.RequestResponder.RespondWithNotFound()
} else {
rc.RequestResponder.RespondWithError(err)
}
return
}
rc.RequestResponder.RespondWithOk(nil, nil)
}
type ModelUpdateF func(id string) error
func Update(rc *response.RequestContext, sc *gojsonschema.Schema, idType response.IdType, in interface{}, updateF ModelUpdateF) {
id, err := rc.GetIdFromRequest(idType)
if err != nil {
log := pfxlog.Logger()
log.Error(err)
rc.RequestResponder.RespondWithError(err)
return
}
var body []byte
if body, err = ioutil.ReadAll(rc.Request.Body); err != nil {
rc.RequestResponder.RespondWithCouldNotReadBody(err)
return
}
if err = unmarshal(body, in); err != nil {
rc.RequestResponder.RespondWithCouldNotParseBody(err)
return
}
il := gojsonschema.NewGoLoader(in)
result, err := sc.Validate(il)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
if !result.Valid() {
var errs []*apierror.ValidationError
for _, re := range result.Errors() {
errs = append(errs, apierror.NewValidationError(re))
}
rc.RequestResponder.RespondWithValidationErrors(errs)
return
}
if err = updateF(id); err != nil {
if util.IsErrNotFoundErr(err) {
rc.RequestResponder.RespondWithNotFound()
return
}
if fe, ok := err.(*model.FieldError); ok {
rc.RequestResponder.RespondWithFieldError(fe)
return
}
rc.RequestResponder.RespondWithError(err)
return
}
rc.RequestResponder.RespondWithOk(nil, nil)
}
type ModelPatchF func(id string, fields JsonFields) error
func Patch(rc *response.RequestContext, sc *gojsonschema.Schema, idType response.IdType, in interface{}, patchF ModelPatchF) {
id, err := rc.GetIdFromRequest(idType)
if err != nil {
log := pfxlog.Logger()
log.Error(err)
rc.RequestResponder.RespondWithError(err)
return
}
var body []byte
if body, err = ioutil.ReadAll(rc.Request.Body); err != nil {
rc.RequestResponder.RespondWithCouldNotReadBody(err)
return
}
if err = unmarshal(body, in); err != nil {
rc.RequestResponder.RespondWithCouldNotParseBody(err)
return
}
jsonFields, err := getFields(body)
if err != nil {
rc.RequestResponder.RespondWithCouldNotParseBody(err)
}
il := gojsonschema.NewGoLoader(in)
result, err := sc.Validate(il)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
if !result.Valid() {
var errs []*apierror.ValidationError
for _, re := range result.Errors() {
errs = append(errs, apierror.NewValidationError(re))
}
rc.RequestResponder.RespondWithValidationErrors(errs)
return
}
err = patchF(id, jsonFields)
if err != nil {
if util.IsErrNotFoundErr(err) {
rc.RequestResponder.RespondWithNotFound()
return
}
if fe, ok := err.(*model.FieldError); ok {
rc.RequestResponder.RespondWithFieldError(fe)
return
}
rc.RequestResponder.RespondWithError(err)
return
}
rc.RequestResponder.RespondWithOk(nil, nil)
}
type ListAssocF func(string, func(model.BaseModelEntity)) error
func ListAssociations(ae *env.AppEnv, rc *response.RequestContext, idType response.IdType, listF ListAssocF, converter ModelToApiMapper) {
id, err := rc.GetIdFromRequest(idType)
if err != nil {
log := pfxlog.Logger()
logErr := fmt.Errorf("could not find id property: %v", response.IdPropertyName)
log.WithField("property", response.IdPropertyName).Error(logErr)
rc.RequestResponder.RespondWithError(err)
return
}
var modelResults []model.BaseModelEntity
err = listF(id, func(entity model.BaseModelEntity) {
modelResults = append(modelResults, entity)
})
if err != nil {
if _, ok := err.(*util.RecordNotFoundError); ok {
rc.RequestResponder.RespondWithNotFound()
return
}
log := pfxlog.Logger()
log.WithField("id", id).WithError(err).Error("could not load associations by id")
rc.RequestResponder.RespondWithError(err)
return
}
subApiEs, err := modelToApi(ae, rc, converter, modelResults)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
count := len(modelResults)
meta := &response.Meta{
"pagination": map[string]interface{}{
"limit": count,
"offset": 0,
"totalCount": count,
},
"filterableFields": []string{},
}
rc.RequestResponder.RespondWithOk(subApiEs, meta)
}
type AssocF func(parentId string, childIds []string) error
func UpdateAssociationsFor(ae *env.AppEnv, rc *response.RequestContext, idType response.IdType, store persistence.Store, action model.AssociationAction, field string) {
UpdateAssociations(ae, rc, idType, func(parentId string, childIds []string) error {
return ae.Handlers.Associations.UpdateAssociations(store, action, field, parentId, childIds...)
})
}
func UpdateAssociations(ae *env.AppEnv, rc *response.RequestContext, idType response.IdType, assocF AssocF) {
parentId, err := rc.GetIdFromRequest(idType)
if err != nil {
log := pfxlog.Logger()
logErr := fmt.Errorf("could not find parentId property: %v", response.IdPropertyName)
log.WithField("property", response.IdPropertyName).
Error(logErr)
rc.RequestResponder.RespondWithError(err)
return
}
body, err := ioutil.ReadAll(rc.Request.Body)
in := &associationIdArrayRequest{}
if err != nil {
rc.RequestResponder.RespondWithError(err)
}
il := gojsonschema.NewBytesLoader(body)
result, err := ae.Schemes.Association.Put.Validate(il)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
if !result.Valid() {
var errs []*apierror.ValidationError
for _, re := range result.Errors() {
errs = append(errs, apierror.NewValidationError(re))
}
rc.RequestResponder.RespondWithValidationErrors(errs)
return
}
err = unmarshal(body, in)
if err != nil {
rc.RequestResponder.RespondWithCouldNotParseBody(err)
return
}
for i, cid := range in.Ids {
_, err := uuid.Parse(cid)
if err != nil {
fieldErr := apierror.NewFieldError(fmt.Sprintf("invalid UUID as ID [%s]: %s", cid, err), fmt.Sprintf("ids[%d]", i), cid)
rc.RequestResponder.RespondWithApiError(apierror.NewField(fieldErr))
return
}
}
err = assocF(parentId, in.Ids)
if err != nil {
if _, ok := err.(*util.RecordNotFoundError); ok {
rc.RequestResponder.RespondWithNotFound()
return
}
if fe, ok := err.(*model.FieldError); ok {
rc.RequestResponder.RespondWithFieldError(fe)
return
}
rc.RequestResponder.RespondWithError(err)
return
}
rc.RequestResponder.RespondWithOk(nil, nil)
}
func RemoveAssociationFor(ae *env.AppEnv, rc *response.RequestContext, idType response.IdType, store persistence.Store, field string) {
RemoveAssociationForModel(rc, idType, func(parentId string, childIds []string) error {
return ae.Handlers.Associations.UpdateAssociations(store, model.AssociationsActionRemove, field, parentId, childIds...)
})
}
func RemoveAssociationForModel(rc *response.RequestContext, idType response.IdType, assocF AssocF) {
parentId, err := rc.GetIdFromRequest(idType)
if err != nil {
log := pfxlog.Logger()
logErr := fmt.Errorf("could not load parent id property: %v", response.IdPropertyName)
log.WithField("property", response.IdPropertyName).
Error(logErr)
rc.RequestResponder.RespondWithError(err)
return
}
subId, err := rc.GetSubIdFromRequest()
if err != nil {
log := pfxlog.Logger()
logErr := fmt.Errorf("could not find assigned entity property: %v", response.SubIdPropertyName)
log.WithField("property", response.SubIdPropertyName).
Error(logErr)
rc.RequestResponder.RespondWithError(err)
return
}
err = assocF(parentId, []string{subId})
if err != nil {
if _, ok := err.(*util.RecordNotFoundError); ok {
rc.RequestResponder.RespondWithNotFound()
return
}
if fe, ok := err.(*model.FieldError); ok {
rc.RequestResponder.RespondWithFieldError(fe)
return
}
log := pfxlog.Logger()
log.WithField("parentId", parentId).
WithField("cause", err).
Errorf("could not load parent record by id [%s]: %s", parentId, err)
rc.RequestResponder.RespondWithError(err)
return
}
rc.RequestResponder.RespondWithOk(nil, nil)
}
@@ -0,0 +1,136 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"encoding/json"
"github.com/stretchr/testify/require"
"reflect"
"testing"
)
var test = `
{
"name" : "Foo",
"dns" : {
"hostname" : "google.com",
"port" : 6433
},
"egressRouter" : "001",
"endpointAddress" : null
}
`
func Test_getFields(t *testing.T) {
assert := require.New(t)
test2 := ServiceApiCreate{
Clusters: []string{"1", "2"},
Dns: &ServiceDnsApiPost{
Hostname: strPtr("google.com"),
Port: uint16Ptr(6433),
},
Name: strPtr("Foo"),
HostIds: nil,
Tags: nil,
EgressRouter: nil,
EndpointAddress: strPtr("tcp:foo:1234"),
}
test2Bytes, err := json.Marshal(test2)
assert.NoError(err)
tests := []struct {
name string
body []byte
want JsonFields
wantErr bool
}{
{
name: "test",
body: []byte(test),
want: JsonFields{
"Name": true,
"Dns.Hostname": true,
"Dns.Port": true,
"EgressRouter": true,
"EndpointAddress": false,
},
wantErr: false,
},
{
name: "test2",
body: test2Bytes,
want: JsonFields{
"Name": true,
"Clusters": true,
"Dns.Hostname": true,
"Dns.Port": true,
"EgressRouter": false,
"EndpointAddress": true,
"HostIds": false,
"Tags": false,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getFields(tt.body)
if (err != nil) != tt.wantErr {
t.Errorf("getFields() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getFields() got = %v, want %v", got, tt.want)
}
})
}
}
func TestJsonFields_ConcatNestedNames(t *testing.T) {
tests := []struct {
name string
j JsonFields
want JsonFields
}{
{"test",
JsonFields{
"Name": true,
"Dns.Hostname": true,
"Dns.Port": true,
"EgressRouter": true,
"EndpointAddress": false,
},
JsonFields{
"Name": true,
"DnsHostname": true,
"DnsPort": true,
"EgressRouter": true,
"EndpointAddress": false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.j.ConcatNestedNames()
if !reflect.DeepEqual(tt.j, tt.want) {
t.Errorf("ConcatNestedNames() got = %v, want %v", tt.j, tt.want)
}
})
}
}
@@ -0,0 +1,161 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/netfoundry/ziti-edge/edge/migration"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/util/stringz"
"net/http"
)
const EntityNameCa = "cas"
type CaApiUpdate struct {
Tags *migration.PropertyMap `json:"tags"`
Name *string `json:"name"`
IsAutoCaEnrollmentEnabled *bool `json:"isAutoCaEnrollmentEnabled"`
IsOttCaEnrollmentEnabled *bool `json:"isOttCaEnrollmentEnabled"`
IsAuthEnabled *bool `json:"isAuthEnabled"`
}
func (i *CaApiUpdate) ToModel(id string) *model.Ca {
result := &model.Ca{}
result.Id = id
result.Name = stringz.OrEmpty(i.Name)
if i.Tags != nil {
result.Tags = *i.Tags
}
result.IsAutoCaEnrollmentEnabled = i.IsAutoCaEnrollmentEnabled != nil && *i.IsAutoCaEnrollmentEnabled
result.IsOttCaEnrollmentEnabled = i.IsOttCaEnrollmentEnabled != nil && *i.IsOttCaEnrollmentEnabled
result.IsAuthEnabled = i.IsAuthEnabled != nil && *i.IsAuthEnabled
return result
}
type CaApiCreate struct {
Tags *migration.PropertyMap `json:"tags"`
Name *string `json:"name"`
CertPem *string `json:"certPem"`
IsAutoCaEnrollmentEnabled *bool `json:"isAutoCaEnrollmentEnabled"`
IsOttCaEnrollmentEnabled *bool `json:"isOttCaEnrollmentEnabled"`
IsAuthEnabled *bool `json:"isAuthEnabled"`
}
func (i *CaApiCreate) ToModel() *model.Ca {
result := &model.Ca{}
result.Name = stringz.OrEmpty(i.Name)
if i.Tags != nil {
result.Tags = *i.Tags
}
result.CertPem = stringz.OrEmpty(i.CertPem)
result.IsAutoCaEnrollmentEnabled = i.IsAutoCaEnrollmentEnabled != nil && *i.IsAutoCaEnrollmentEnabled
result.IsOttCaEnrollmentEnabled = i.IsOttCaEnrollmentEnabled != nil && *i.IsOttCaEnrollmentEnabled
result.IsAuthEnabled = i.IsAuthEnabled != nil && *i.IsAuthEnabled
return result
}
type CaApiList struct {
*env.BaseApi
Name *string `json:"name"`
Fingerprint *string `json:"fingerprint"`
CertPem *string `json:"certPem"`
IsVerified *bool `json:"isVerified"`
VerificationToken *string `json:"verificationToken"`
IsAutoCaEnrollmentEnabled *bool `json:"isAutoCaEnrollmentEnabled"`
IsOttCaEnrollmentEnabled *bool `json:"isOttCaEnrollmentEnabled"`
IsAuthEnabled *bool `json:"isAuthEnabled"`
}
func (CaApiList) BuildSelfLink(id string) *response.Link {
return response.NewLink(fmt.Sprintf("./%s/%s", EntityNameCa, id))
}
func (e *CaApiList) GetSelfLink() *response.Link {
return e.BuildSelfLink(e.Id)
}
func (e *CaApiList) PopulateLinks() {
if e.Links == nil {
e.Links = &response.Links{
EntityNameSelf: e.GetSelfLink(),
}
if !*e.IsVerified {
vl := response.NewLink(fmt.Sprintf("%s/verify", (*e.Links)[EntityNameSelf].Href))
vl.Method = http.MethodPost
(*e.Links)["verify"] = vl
}
if *e.IsAutoCaEnrollmentEnabled {
jl := response.NewLink(fmt.Sprintf("%s/jwt", (*e.Links)[EntityNameSelf].Href))
jl.Method = http.MethodGet
(*e.Links)["jwt"] = jl
}
}
}
func (e *CaApiList) ToEntityApiRef() *EntityApiRef {
e.PopulateLinks()
return &EntityApiRef{
Entity: EntityNameCa,
Name: e.Name,
Id: e.Id,
Links: e.Links,
}
}
func MapCaToApiEntity(_ *env.AppEnv, _ *response.RequestContext, e model.BaseModelEntity) (BaseApiEntity, error) {
i, ok := e.(*model.Ca)
if !ok {
err := fmt.Errorf("entity is not a CA \"%s\"", e.GetId())
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
al, err := MapCaToApiList(i)
if err != nil {
err := fmt.Errorf("could not convert to API entity \"%s\": %s", e.GetId(), err)
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
return al, nil
}
func MapCaToApiList(i *model.Ca) (*CaApiList, error) {
ret := &CaApiList{
BaseApi: env.FromBaseModelEntity(i),
Name: &i.Name,
VerificationToken: &i.VerificationToken,
IsVerified: &i.IsVerified,
CertPem: &i.CertPem,
Fingerprint: &i.Fingerprint,
IsAuthEnabled: &i.IsAuthEnabled,
IsAutoCaEnrollmentEnabled: &i.IsAutoCaEnrollmentEnabled,
IsOttCaEnrollmentEnabled: &i.IsOttCaEnrollmentEnabled,
}
ret.PopulateLinks()
return ret, nil
}
@@ -0,0 +1,271 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/apierror"
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/internal/permissions"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/netfoundry/ziti-edge/edge/controller/util"
"github.com/netfoundry/ziti-edge/sdk/ziti/config"
"crypto/x509"
"encoding/pem"
"fmt"
jwt2 "github.com/dgrijalva/jwt-go"
"github.com/michaelquigley/pfxlog"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
)
func init() {
r := NewCaRouter()
env.AddRouter(r)
}
type CaRouter struct {
BasePath string
IdType response.IdType
}
func NewCaRouter() *CaRouter {
return &CaRouter{
BasePath: "/" + EntityNameCa,
IdType: response.IdTypeUuid,
}
}
func (ir *CaRouter) Register(ae *env.AppEnv) {
sr := registerCrudRouter(ae, ae.RootRouter, ir.BasePath, ir, permissions.IsAdmin())
idUrlWithoutSlash := fmt.Sprintf("/{%s}/verify", response.IdPropertyName)
idUrlWithSlash := fmt.Sprintf("/{%s}/verify/", response.IdPropertyName)
verifyHandler := ae.WrapHandler(ir.VerifyCert, permissions.IsAdmin())
sr.HandleFunc(idUrlWithoutSlash, verifyHandler).Methods(http.MethodPost)
sr.HandleFunc(idUrlWithSlash, verifyHandler).Methods(http.MethodPost)
getJwtWithSlash := fmt.Sprintf("/{%s}/jwt", response.IdPropertyName)
getJwtWithoutSlash := fmt.Sprintf("/{%s}/jwt/", response.IdPropertyName)
jwtHandler := ae.WrapHandler(ir.generateJwt, permissions.IsAdmin())
sr.HandleFunc(getJwtWithSlash, jwtHandler).Methods(http.MethodGet)
sr.HandleFunc(getJwtWithoutSlash, jwtHandler).Methods(http.MethodGet)
}
func (ir *CaRouter) List(ae *env.AppEnv, rc *response.RequestContext) {
ListWithHandler(ae, rc, ae.Handlers.Ca, MapCaToApiEntity)
}
func (ir *CaRouter) Detail(ae *env.AppEnv, rc *response.RequestContext) {
DetailWithHandler(ae, rc, ae.Handlers.Ca, MapCaToApiEntity, ir.IdType)
}
func (ir *CaRouter) Create(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &CaApiCreate{}
Create(rc, rc.RequestResponder, ae.Schemes.Ca.Post, apiEntity, (&CaApiList{}).BuildSelfLink, func() (string, error) {
return ae.Handlers.Ca.HandleCreate(apiEntity.ToModel())
})
}
func (ir *CaRouter) Delete(ae *env.AppEnv, rc *response.RequestContext) {
DeleteWithHandler(rc, ir.IdType, ae.Handlers.Ca)
}
func (ir *CaRouter) Update(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &CaApiUpdate{}
Update(rc, ae.Schemes.Ca.Put, ir.IdType, apiEntity, func(id string) error {
return ae.Handlers.Ca.HandleUpdate(apiEntity.ToModel(id))
})
}
func (ir *CaRouter) Patch(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &CaApiUpdate{}
Patch(rc, ae.Schemes.Ca.Patch, ir.IdType, apiEntity, func(id string, fields JsonFields) error {
return ae.Handlers.Ca.HandlePatch(apiEntity.ToModel(id), fields.ConcatNestedNames())
})
}
func (ir *CaRouter) VerifyCert(ae *env.AppEnv, rc *response.RequestContext) {
id, err := rc.GetIdFromRequest(ir.IdType)
if err != nil {
log := pfxlog.Logger()
err := fmt.Errorf("could not find id property: %v", response.IdPropertyName)
log.Error(err)
rc.RequestResponder.RespondWithNotFound()
return
}
ca, err := ae.Handlers.Ca.HandleRead(id)
if err != nil {
if _, ok := err.(*util.RecordNotFoundError); ok {
rc.RequestResponder.RespondWithNotFound()
return
}
log := pfxlog.Logger()
log.WithField("id", id).WithField("cause", err).
Errorf("could not load identity by id [%s]: %s", id, err)
rc.RequestResponder.RespondWithError(err)
return
}
if ca == nil {
rc.RequestResponder.RespondWithNotFound()
return
}
if ca.IsVerified {
rc.RequestResponder.RespondWithApiError(apierror.NewCaAlreadyVerified())
return
}
body, err := ioutil.ReadAll(rc.Request.Body)
if err != nil || len(body) == 0 {
rc.RequestResponder.RespondWithCouldNotParseBody(err)
return
}
der, _ := pem.Decode(body)
if der == nil {
apiErr := apierror.NewCouldNotParseBody()
apiErr.Cause = err
apiErr.AppendCause = true
rc.RequestResponder.RespondWithApiError(apiErr)
return
}
if der.Type != "CERTIFICATE" {
apiErr := apierror.NewExpectedPemBlockCertificate()
apiErr.Cause = fmt.Errorf("ecountered PEM block type %s", der.Type)
rc.RequestResponder.RespondWithApiError(apiErr)
return
}
cert, err := x509.ParseCertificate(der.Bytes)
if err != nil {
apiErr := apierror.NewCouldNotParseDerBlock()
apiErr.AppendCause = true
apiErr.Cause = err
rc.RequestResponder.RespondWithApiError(apiErr)
return
}
if cert.Subject.CommonName != ca.VerificationToken {
rc.RequestResponder.RespondWithApiError(apierror.NewInvalidCommonName())
return
}
caDer, _ := pem.Decode([]byte(ca.CertPem))
caCert, err := x509.ParseCertificate(caDer.Bytes)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
roots := x509.NewCertPool()
roots.AddCert(caCert)
_, err = cert.Verify(x509.VerifyOptions{
Roots: roots,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
})
if err != nil {
apiErr := apierror.NewFailedCertificateValidation()
apiErr.Cause = err
rc.RequestResponder.RespondWithApiError(apiErr)
return
}
err = ae.Handlers.Ca.HandleVerified(ca)
if err != nil {
rc.RequestResponder.RespondWithError(err)
return
}
rc.RequestResponder.RespondWithOk(nil, nil)
}
func (ir *CaRouter) generateJwt(ae *env.AppEnv, rc *response.RequestContext) {
id, getErr := rc.GetIdFromRequest(ir.IdType)
if getErr != nil {
log := pfxlog.Logger()
err := fmt.Errorf("could not find id property: %v", response.IdPropertyName)
log.Error(err)
rc.RequestResponder.RespondWithNotFound()
return
}
ca, loadErr := ae.Handlers.Ca.HandleRead(id)
if loadErr != nil {
if _, ok := loadErr.(*util.RecordNotFoundError); ok {
rc.RequestResponder.RespondWithNotFound()
return
}
log := pfxlog.Logger()
log.Errorf("could not load identity by id \"%s\": %s", id, loadErr)
rc.RequestResponder.RespondWithError(loadErr)
return
}
if ca == nil {
rc.RequestResponder.RespondWithNotFound()
return
}
var notAfter int64
method := "ca"
claims := &config.EnrollmentClaims{
EnrollmentMethod: method,
StandardClaims: jwt2.StandardClaims{
ExpiresAt: notAfter,
Issuer: fmt.Sprintf(`https://%s/`, ae.Config.Api.Advertise),
},
}
mapClaims, err := claims.ToMapClaims()
if err != nil {
rc.RequestResponder.RespondWithError(fmt.Errorf("could not convert CA enrollment claims to interface map: %s", err))
return
}
jwt, genErr := ae.EnrollmentJwtGenerator.Generate(ca.Id, ca.Id, mapClaims)
if genErr != nil {
rc.RequestResponder.RespondWithError(errors.New("could not generate claims"))
return
}
rc.ResponseWriter.Header().Set("Content-Type", "application/jwt")
response.AddVersionHeader(rc.ResponseWriter)
rc.ResponseWriter.WriteHeader(http.StatusOK)
_, _ = rc.ResponseWriter.Write([]byte(jwt))
}
@@ -0,0 +1,109 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/model"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"github.com/netfoundry/ziti-edge/edge/migration"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/util/stringz"
)
const EntityNameCluster = "clusters"
type ClusterApi struct {
Tags *migration.PropertyMap `json:"tags"`
Name *string `json:"name"`
}
func (i *ClusterApi) ToModel(id string) *model.Cluster {
result := &model.Cluster{}
result.Id = id
result.Name = stringz.OrEmpty(i.Name)
if i.Tags != nil {
result.Tags = *i.Tags
}
return result
}
type ClusterApiList struct {
*env.BaseApi
Name *string `json:"name"`
}
func (c *ClusterApiList) GetSelfLink() *response.Link {
return c.BuildSelfLink(c.Id)
}
func (ClusterApiList) BuildSelfLink(id string) *response.Link {
return response.NewLink(fmt.Sprintf("./%s/%s", EntityNameCluster, id))
}
func (c *ClusterApiList) PopulateLinks() {
if c.Links == nil {
self := c.GetSelfLink()
c.Links = &response.Links{
EntityNameSelf: self,
EntityNameEdgeRouter: response.NewLink(fmt.Sprintf(self.Href + "/" + EntityNameEdgeRouter)),
}
}
}
func (c *ClusterApiList) ToEntityApiRef() *EntityApiRef {
c.PopulateLinks()
return &EntityApiRef{
Entity: EntityNameCluster,
Name: c.Name,
Id: c.Id,
Links: c.Links,
}
}
func MapClusterToApiEntity(_ *env.AppEnv, _ *response.RequestContext, e model.BaseModelEntity) (BaseApiEntity, error) {
i, ok := e.(*model.Cluster)
if !ok {
err := fmt.Errorf("entity is not a cluster \"%s\"", e.GetId())
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
al, err := MapClusterToApiList(i)
if err != nil {
err := fmt.Errorf("could not convert to API entity \"%s\": %s", e.GetId(), err)
log := pfxlog.Logger()
log.Error(err)
return nil, err
}
return al, nil
}
func MapClusterToApiList(i *model.Cluster) (*ClusterApiList, error) {
ret := &ClusterApiList{
BaseApi: env.FromBaseModelEntity(i),
Name: &i.Name,
}
ret.PopulateLinks()
return ret, nil
}
@@ -0,0 +1,113 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
"github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/internal/permissions"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"fmt"
"net/http"
)
func init() {
r := NewClusterRouter()
env.AddRouter(r)
}
type ClusterRouter struct {
BasePath string
IdType response.IdType
}
func NewClusterRouter() *ClusterRouter {
return &ClusterRouter{
BasePath: "/" + EntityNameCluster,
IdType: response.IdTypeUuid,
}
}
func (ir *ClusterRouter) Register(ae *env.AppEnv) {
sr := registerCrudRouter(ae, ae.RootRouter, ir.BasePath, ir, permissions.IsAdmin())
edgeRouterUrlWithSlash := fmt.Sprintf("/{%s}/%s", response.IdPropertyName, EntityNameEdgeRouter)
gatewayUrlWithSlash := fmt.Sprintf("/{%s}/%s", response.IdPropertyName, EntityNameGateway)
edgeRouterWithOutSlash := fmt.Sprintf("/{%s}/%s/", response.IdPropertyName, EntityNameEdgeRouter)
gatewayUrlWithOutSlash := fmt.Sprintf("/{%s}/%s/", response.IdPropertyName, EntityNameGateway)
edgeRoutersListHandler := ae.WrapHandler(ir.ListEdgeRouters, permissions.IsAdmin())
edgeRoutersAddHandler := ae.WrapHandler(ir.AddEdgeRouters, permissions.IsAdmin())
//gets
sr.HandleFunc(gatewayUrlWithSlash, edgeRoutersListHandler).Methods(http.MethodGet)
sr.HandleFunc(edgeRouterUrlWithSlash, edgeRoutersListHandler).Methods(http.MethodGet)
sr.HandleFunc(gatewayUrlWithOutSlash, edgeRoutersListHandler).Methods(http.MethodGet)
sr.HandleFunc(edgeRouterWithOutSlash, edgeRoutersListHandler).Methods(http.MethodGet)
//puts
sr.HandleFunc(gatewayUrlWithSlash, edgeRoutersAddHandler).Methods(http.MethodPut)
sr.HandleFunc(edgeRouterUrlWithSlash, edgeRoutersAddHandler).Methods(http.MethodPut)
sr.HandleFunc(gatewayUrlWithOutSlash, edgeRoutersAddHandler).Methods(http.MethodPut)
sr.HandleFunc(edgeRouterWithOutSlash, edgeRoutersAddHandler).Methods(http.MethodPut)
}
func (ir *ClusterRouter) List(ae *env.AppEnv, rc *response.RequestContext) {
ListWithHandler(ae, rc, ae.Handlers.Cluster, MapClusterToApiEntity)
}
func (ir *ClusterRouter) Detail(ae *env.AppEnv, rc *response.RequestContext) {
DetailWithHandler(ae, rc, ae.Handlers.Cluster, MapClusterToApiEntity, ir.IdType)
}
func (ir *ClusterRouter) Create(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &ClusterApi{}
Create(rc, rc.RequestResponder, ae.Schemes.Cluster.Post, apiEntity, (&ClusterApiList{}).BuildSelfLink, func() (string, error) {
return ae.Handlers.Cluster.HandleCreate(apiEntity.ToModel(""))
})
}
func (ir *ClusterRouter) Delete(ae *env.AppEnv, rc *response.RequestContext) {
DeleteWithHandler(rc, ir.IdType, ae.Handlers.Cluster)
}
func (ir *ClusterRouter) Update(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &ClusterApi{}
Update(rc, ae.Schemes.Cluster.Put, ir.IdType, apiEntity, func(id string) error {
return ae.Handlers.Cluster.HandleUpdate(apiEntity.ToModel(id))
})
}
func (ir *ClusterRouter) Patch(ae *env.AppEnv, rc *response.RequestContext) {
apiEntity := &ClusterApi{}
Patch(rc, ae.Schemes.Cluster.Patch, ir.IdType, apiEntity, func(id string, fields JsonFields) error {
return ae.Handlers.Cluster.HandlePatch(apiEntity.ToModel(id), fields.ConcatNestedNames())
})
}
func (ir *ClusterRouter) ListEdgeRouters(ae *env.AppEnv, rc *response.RequestContext) {
ListAssociations(ae, rc, ir.IdType, ae.Handlers.Cluster.HandleCollectEdgeRouters, MapEdgeRouterToApiEntity)
}
func (ir *ClusterRouter) AddEdgeRouters(ae *env.AppEnv, rc *response.RequestContext) {
UpdateAssociations(ae, rc, ir.IdType, func(parentId string, childIds []string) error {
return ae.Handlers.Cluster.HandleAddEdgeRouters(parentId, childIds)
})
}
@@ -0,0 +1,59 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 routes
import (
env2 "github.com/netfoundry/ziti-edge/edge/controller/env"
"github.com/netfoundry/ziti-edge/edge/controller/response"
"time"
)
const EntityNameCurrentSession = "current-session"
type CurrentSessionApiList struct {
*env2.BaseApi
Token *string `json:"token"`
Identity *EntityApiRef `json:"identity"`
ExpiresAt *time.Time `json:"expiresAt"`
}
func (CurrentSessionApiList) BuildSelfLink(_ string) *response.Link {
return response.NewLink("./" + EntityNameCurrentSession)
}
func (e *CurrentSessionApiList) GetSelfLink() *response.Link {
return e.BuildSelfLink(e.Id)
}
func (e *CurrentSessionApiList) PopulateLinks() {
if e.Links == nil {
e.Links = &response.Links{
EntityNameSelf: e.GetSelfLink(),
}
}
}
func (e *CurrentSessionApiList) ToEntityApiRef() *EntityApiRef {
e.PopulateLinks()
return &EntityApiRef{
Entity: EntityNameCurrentSession,
Name: nil,
Id: e.Id,
Links: e.Links,
}
}

Some files were not shown because too many files have changed in this diff Show More