mirror of
https://github.com/openziti/ziti.git
synced 2026-10-12 23:32:02 +00:00
adds ability to verify a ca to the CLI
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 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 edge_controller
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
cmdutil "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/factory"
|
||||
cmdhelper "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/helpers"
|
||||
"github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// newVerifyCmd creates a command object for the "controller verify" command
|
||||
func newVerifyCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "verifies various entities managed by the Ziti Edge Controller",
|
||||
Long: "Verifies various entities managed by the Ziti Edge Controller",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := cmd.Help()
|
||||
cmdhelper.CheckErr(err)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(newVerifyCaCmd(f, out, errOut))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// createEntityOfType create an entity of the given type on the Ziti Edge Controller
|
||||
func verifyEntityOfType(entityType, body, id string, options *commonOptions) error {
|
||||
return util.EdgeControllerVerify(entityType, id, body, options.Out, options.OutputJSONResponse)
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
Copyright 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 edge_controller
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/common"
|
||||
cmdutil "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/factory"
|
||||
cmdhelper "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/helpers"
|
||||
"github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/util"
|
||||
nfpem "github.com/netfoundry/ziti-foundation/util/pem"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/resty.v1"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
type verifyCaOptions struct {
|
||||
commonOptions
|
||||
|
||||
certPath string
|
||||
certPemBytes []byte
|
||||
|
||||
caNameOrId string
|
||||
caId string
|
||||
|
||||
caCertPath string
|
||||
caCertPemBytes []byte
|
||||
|
||||
caKeyPath string
|
||||
caKeyPemBytes []byte
|
||||
|
||||
isGenerateCert bool
|
||||
}
|
||||
|
||||
// newVerifyCaCmd creates the 'edge controller verify ca' command for the given entity type
|
||||
func newVerifyCaCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
options := &verifyCaOptions{
|
||||
commonOptions: commonOptions{
|
||||
CommonOptions: common.CommonOptions{
|
||||
Factory: f,
|
||||
Out: out,
|
||||
Err: errOut,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ca <name> [ --cert <pemCertFile> | --cacert <signingCaCert> --cakey <signingCaKey>]",
|
||||
Short: "verifies a ca managed by the Ziti Edge Controller",
|
||||
Long: "verifies a ca managed by the Ziti Edge Controller. If --cert is supplied, it is expected that it is a certificate with the " +
|
||||
"common name set to the proper verificationToken value from the target CA. If not set, --cakey and --cacert can be provided to " +
|
||||
"generate the signed certificate and submit it.",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("requires at least %d arg(s), only received %d", 1, len(args))
|
||||
}
|
||||
|
||||
options.caNameOrId = args[0]
|
||||
|
||||
if options.certPath != "" {
|
||||
var err error
|
||||
options.certPemBytes, err = ioutil.ReadFile(options.certPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read --cert file (%s): %v", options.certPath, err)
|
||||
}
|
||||
options.isGenerateCert = false
|
||||
} else if options.caCertPath != "" && options.caKeyPath != "" {
|
||||
var err error
|
||||
options.caKeyPemBytes, err = ioutil.ReadFile(options.caKeyPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read --cakey file (%s): %v", options.caKeyPath, err)
|
||||
}
|
||||
|
||||
options.caCertPemBytes, err = ioutil.ReadFile(options.caCertPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read --cacert file (%s): %v", options.caCertPath, err)
|
||||
}
|
||||
options.isGenerateCert = true
|
||||
} else {
|
||||
return errors.New("expected either (--cert <pemCertFile) or (--cacert <signingCaCert> --cakey <signingCaKey>), some options were missing")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
options.Cmd = cmd
|
||||
options.Args = args
|
||||
err := runValidateCa(options)
|
||||
cmdhelper.CheckErr(err)
|
||||
},
|
||||
SuggestFor: []string{},
|
||||
}
|
||||
|
||||
// allow interspersing positional args and flags
|
||||
cmd.Flags().SetInterspersed(true)
|
||||
cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller")
|
||||
cmd.Flags().StringVarP(&options.certPath, "cert", "c", "", "The path to a cert with the CN set as the verification token and signed by the target CA")
|
||||
cmd.Flags().StringVarP(&options.caKeyPath, "cacert", "a", "", "The path to the CA cert that should be used to generate and sign a verification cert")
|
||||
cmd.Flags().StringVarP(&options.caKeyPath, "cakey", "k", "", "The path to the CA key that should be used to generate and sign a verification cert")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runValidateCa(options *verifyCaOptions) error {
|
||||
var err error
|
||||
options.caId, err = mapCaNameToID(options.caNameOrId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.isGenerateCert {
|
||||
jsonContainer, err := util.EdgeControllerRequest("ca/"+options.caId, options.Out, options.OutputJSONResponse, func(request *resty.Request, url string) (*resty.Response, error) { return request.Get(url) })
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not request ca [%s] (%v}", options.caId, err)
|
||||
}
|
||||
|
||||
token := jsonContainer.Path("data.verificationToken").Data().(string)
|
||||
|
||||
if token == "" {
|
||||
return fmt.Errorf("could not obtain verification token for ca [%s]", options.caId)
|
||||
}
|
||||
|
||||
options.certPemBytes, _, err = generateCert(options, token)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate validation cert: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return util.EdgeControllerVerify("ca", options.caId, string(options.certPemBytes), options.Out, options.OutputJSONResponse)
|
||||
}
|
||||
|
||||
func generateCert(options *verifyCaOptions, token string) ([]byte, crypto.Signer, error) {
|
||||
|
||||
certBlocks := nfpem.PemToX509(string(options.caCertPemBytes))
|
||||
|
||||
if len(certBlocks) == 0 {
|
||||
return nil, nil, errors.New("could not prase cert file, 0 PEM blocks detected1")
|
||||
}
|
||||
|
||||
caCertBlock := certBlocks[0]
|
||||
caCert, err := x509.ParseCertificate(caCertBlock.Raw)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not parse ca cert (%v)", err)
|
||||
}
|
||||
|
||||
keyBlocks := nfpem.DecodeAll(options.caKeyPemBytes)
|
||||
|
||||
if len(keyBlocks) == 0 {
|
||||
return nil, nil, errors.New("could not parse key file, 0 PEM blocks detected")
|
||||
}
|
||||
|
||||
caPrivateKeyBlock := keyBlocks[0]
|
||||
signerInterface, err := x509.ParsePKCS8PrivateKey(caPrivateKeyBlock.Bytes)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not parse key file (%s)", err)
|
||||
}
|
||||
|
||||
caKey := signerInterface.(crypto.Signer)
|
||||
|
||||
if caKey == nil {
|
||||
return nil, nil, fmt.Errorf("key was not of correct type, could not be used as signer")
|
||||
}
|
||||
|
||||
id, _ := rand.Int(rand.Reader, big.NewInt(100000000000000000))
|
||||
verificationCert := &x509.Certificate{
|
||||
SerialNumber: id,
|
||||
Subject: pkix.Name{
|
||||
CommonName: token,
|
||||
Organization: []string{"Ziti CLI Generated Validation Cert"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Minute * 5),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
verificationKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not generate private key for verification certificate (%v)", err)
|
||||
}
|
||||
|
||||
signedCertBytes, err := x509.CreateCertificate(rand.Reader, verificationCert, caCert, verificationKey.PublicKey, caKey)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not sign verification certificate with CA (%v)", err)
|
||||
}
|
||||
|
||||
verificationCert, _ = x509.ParseCertificate(signedCertBytes)
|
||||
|
||||
verificationBlock := &pem.Block{
|
||||
Type: "EC CERTIFICATE",
|
||||
Bytes: verificationCert.Raw,
|
||||
}
|
||||
|
||||
verificationCertPemBytes := pem.EncodeToMemory(verificationBlock)
|
||||
|
||||
return verificationCertPemBytes, verificationKey, nil
|
||||
}
|
||||
|
||||
//func runCreateCa(options *createCaOptions) (err error) {
|
||||
// data := gabs.New()
|
||||
// setJSONValue(data, options.name, "name")
|
||||
// setJSONValue(data, options.autoCaEnrollment, "isAutoCaEnrollmentEnabled")
|
||||
// setJSONValue(data, options.ottCaEnrollment, "isOttCaEnrollmentEnabled")
|
||||
// setJSONValue(data, options.authEnabled, "isAuthEnabled")
|
||||
// setJSONValue(data, string(options.caPemBytes), "certPem")
|
||||
//
|
||||
// result, err := createEntityOfType("cas", data.String(), &options.commonOptions)
|
||||
//
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// id := result.S("data", "id").Data()
|
||||
//
|
||||
// if _, err = fmt.Fprintf(options.Out, "%v\n", id); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// return err
|
||||
//}
|
||||
@@ -625,6 +625,41 @@ func EdgeControllerUpdate(entityType string, body string, out io.Writer, put boo
|
||||
return jsonParsed, nil
|
||||
}
|
||||
|
||||
// EdgeControllerVerify will create entities of the given type in the given Edge Controller
|
||||
func EdgeControllerVerify(entityType, id, body string, out io.Writer, logJSON bool) error {
|
||||
session := &Session{}
|
||||
if err := session.Load(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := newClient()
|
||||
|
||||
if session.Cert != "" {
|
||||
client.SetRootCertificate(session.Cert)
|
||||
}
|
||||
resp, err := client.
|
||||
R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader(constants.ZitiSession, session.Token).
|
||||
SetBody(body).
|
||||
Post(session.Host + "/" + entityType + "/" + id + "/verify")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to verify %v instance [%s] in Ziti Edge Controller at %v. Error: %v", entityType, id, session.Host, err)
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return fmt.Errorf("error verifying %v instance (%v) in Ziti Edge Controller at %v. Status code: %v, Server returned: %v",
|
||||
entityType, id, session.Host, resp.Status(), resp.String())
|
||||
}
|
||||
|
||||
if logJSON {
|
||||
outputJson(out, resp.Body())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EdgeControllerRequest(entityType string, out io.Writer, logJSON bool, doRequest func(*resty.Request, string) (*resty.Response, error)) (*gabs.Container, error) {
|
||||
session := &Session{}
|
||||
if err := session.Load(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user