From bffb8eec65ba741d16c9399b72ecee07af00d1a1 Mon Sep 17 00:00:00 2001 From: wanyaoqi Date: Tue, 21 Apr 2020 16:11:57 +0800 Subject: [PATCH] keystone store service cert --- cmd/climc/shell/endpoints.go | 34 +++-- cmd/climc/shell/servicecertificates.go | 60 ++++++++ pkg/apis/compute/zz_generated.model.go | 12 +- pkg/apis/identity/endpoint.go | 10 ++ pkg/apis/identity/zz_generated.model.go | 23 +++- pkg/apis/zz_generated.model.go | 15 ++ pkg/cloudcommon/db/certificateresource.go | 20 +++ pkg/cloudcommon/validators/validators_cert.go | 71 ++++++++++ .../validators/validators_cert_test.go | 106 +++++++++++++++ .../models/loadbalancercertificates.go | 71 +--------- pkg/keystone/models/endpoints.go | 68 ++++++++-- pkg/keystone/models/servicecertificates.go | 128 ++++++++++++++++++ pkg/keystone/service/handlers.go | 1 + .../modules/mod_service_certficates.go | 26 ++++ pkg/mcclient/options/servicecertificates.go | 66 +++++++++ 15 files changed, 605 insertions(+), 106 deletions(-) create mode 100644 cmd/climc/shell/servicecertificates.go create mode 100644 pkg/cloudcommon/db/certificateresource.go create mode 100644 pkg/keystone/models/servicecertificates.go create mode 100644 pkg/mcclient/modules/mod_service_certficates.go create mode 100644 pkg/mcclient/options/servicecertificates.go diff --git a/cmd/climc/shell/endpoints.go b/cmd/climc/shell/endpoints.go index ef3745a74e..e07448a6be 100644 --- a/cmd/climc/shell/endpoints.go +++ b/cmd/climc/shell/endpoints.go @@ -64,14 +64,15 @@ func init() { }) type EndpointCreateOptions struct { - SERVICE string `help:"Service ID or Name"` - REGION string `help:"Region"` - INTERFACE string `help:"Interface types" choices:"internal|public|admin|console"` - URL string `help:"URL"` - Zone string `help:"Zone"` - Name string `help:"Name"` - Enabled bool `help:"Enabled"` - Disabled bool `help:"Disabled"` + SERVICE string `help:"Service ID or Name"` + REGION string `help:"Region"` + INTERFACE string `help:"Interface types" choices:"internal|public|admin|console"` + URL string `help:"URL"` + Zone string `help:"Zone"` + Name string `help:"Name"` + Enabled bool `help:"Enabled"` + Disabled bool `help:"Disabled"` + ServiceCertificate string `help:"Service certificate id or name"` } R(&EndpointCreateOptions{}, "endpoint-create", "Create endpoint", func(s *mcclient.ClientSession, args *EndpointCreateOptions) error { params := jsonutils.NewDict() @@ -95,6 +96,9 @@ func init() { } else if !args.Enabled && args.Disabled { params.Add(jsonutils.JSONFalse, "enabled") } + if len(args.ServiceCertificate) > 0 { + params.Add(jsonutils.NewString(args.ServiceCertificate), "service_certificate") + } ep, err := modules.EndpointsV3.Create(s, params) if err != nil { return err @@ -104,11 +108,12 @@ func init() { }) type EndpointUpdateOptions struct { - ID string `help:"ID or name of endpoint"` - Url string `help:"URL"` - Name string `help:"Name"` - Enabled bool `help:"Enabled"` - Disabled bool `help:"Disabled"` + ID string `help:"ID or name of endpoint"` + Url string `help:"URL"` + Name string `help:"Name"` + Enabled bool `help:"Enabled"` + Disabled bool `help:"Disabled"` + ServiceCertificate string `help:"Service certificate id or name"` } R(&EndpointUpdateOptions{}, "endpoint-update", "Update a endpoint", func(s *mcclient.ClientSession, args *EndpointUpdateOptions) error { params := jsonutils.NewDict() @@ -123,6 +128,9 @@ func init() { } else if !args.Enabled && args.Disabled { params.Add(jsonutils.JSONFalse, "enabled") } + if len(args.ServiceCertificate) > 0 { + params.Add(jsonutils.NewString(args.ServiceCertificate), "service_certificate") + } ep, err := modules.EndpointsV3.Patch(s, args.ID, params) if err != nil { return err diff --git a/cmd/climc/shell/servicecertificates.go b/cmd/climc/shell/servicecertificates.go new file mode 100644 index 0000000000..96103899cd --- /dev/null +++ b/cmd/climc/shell/servicecertificates.go @@ -0,0 +1,60 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + R(&options.ServiceCertificateCreateOptions{}, "service-cert-create", "Create service cert", func(s *mcclient.ClientSession, opts *options.ServiceCertificateCreateOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + cert, err := modules.ServiceCertificatesV3.Create(s, params) + if err != nil { + return err + } + printObject(cert) + return nil + }) + type ServiceCertificateGetOptions struct { + ID string `json:"-"` + } + R(&ServiceCertificateGetOptions{}, "service-cert-show", "Show service cert", func(s *mcclient.ClientSession, opts *ServiceCertificateGetOptions) error { + cert, err := modules.ServiceCertificatesV3.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObject(cert) + return nil + }) + + type ServiceCertificateListOptions struct { + options.BaseListOptions + } + R(&ServiceCertificateListOptions{}, "service-cert-list", "List service certs", func(s *mcclient.ClientSession, opts *ServiceCertificateListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.ServiceCertificatesV3.List(s, params) + if err != nil { + return err + } + printList(result, modules.ServiceCertificatesV3.GetColumns(s)) + return nil + }) + type ServiceCertificateDeleteOptions struct { + ID string `json:"-"` + } + R(&ServiceCertificateDeleteOptions{}, "service-cert-delete", "Delete service cert", func(s *mcclient.ClientSession, opts *ServiceCertificateDeleteOptions) error { + cert, err := modules.ServiceCertificatesV3.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObject(cert) + return nil + }) +} diff --git a/pkg/apis/compute/zz_generated.model.go b/pkg/apis/compute/zz_generated.model.go index bd320f93b3..9578f19986 100644 --- a/pkg/apis/compute/zz_generated.model.go +++ b/pkg/apis/compute/zz_generated.model.go @@ -1297,17 +1297,7 @@ type SLoadbalancerBackendgroupResourceBase struct { type SLoadbalancerCertificate struct { apis.SVirtualResourceBase apis.SExternalizedResourceBase - Certificate string `json:"certificate"` - PrivateKey string `json:"private_key"` - // derived attributes - PublicKeyAlgorithm string `json:"public_key_algorithm"` - PublicKeyBitLen int `json:"public_key_bit_len"` - SignatureAlgorithm string `json:"signature_algorithm"` - Fingerprint string `json:"fingerprint"` - NotBefore time.Time `json:"not_before"` - NotAfter time.Time `json:"not_after"` - CommonName string `json:"common_name"` - SubjectAlternativeNames string `json:"subject_alternative_names"` + apis.SCertificateResourceBase } // SLoadbalancerCertificateResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SLoadbalancerCertificateResourceBase. diff --git a/pkg/apis/identity/endpoint.go b/pkg/apis/identity/endpoint.go index 2e7c675ab3..ac9e96ea39 100644 --- a/pkg/apis/identity/endpoint.go +++ b/pkg/apis/identity/endpoint.go @@ -19,6 +19,7 @@ import "yunion.io/x/onecloud/pkg/apis" type EndpointDetails struct { apis.StandaloneResourceDetails SEndpoint + CertificateDetails // 服务名称,例如keystone, glance, region等 ServiceName string `json:"service_name"` @@ -26,3 +27,12 @@ type EndpointDetails struct { // 服务类型,例如identity, image, compute等 ServiceType string `json:"service_type"` } + +type CertificateDetails struct { + apis.SCertificateResourceBase + CertName string `json:"cert_name"` + CertId string `json:"cert_id"` + + CaCertificate string `json:"ca_certificate"` + CaPrivateKey string `json:"ca_private_key"` +} diff --git a/pkg/apis/identity/zz_generated.model.go b/pkg/apis/identity/zz_generated.model.go index dabaedf6e4..0a6a763ccf 100644 --- a/pkg/apis/identity/zz_generated.model.go +++ b/pkg/apis/identity/zz_generated.model.go @@ -71,13 +71,14 @@ type SEnabledIdentityBaseResource struct { // SEndpoint is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SEndpoint. type SEndpoint struct { apis.SStandaloneResourceBase - LegacyEndpointId string `json:"legacy_endpoint_id"` - Interface string `json:"interface"` - ServiceId string `json:"service_id"` - Url string `json:"url"` - Extra interface{} `json:"extra"` - Enabled *bool `json:"enabled,omitempty"` - RegionId string `json:"region_id"` + LegacyEndpointId string `json:"legacy_endpoint_id"` + Interface string `json:"interface"` + ServiceId string `json:"service_id"` + Url string `json:"url"` + Extra interface{} `json:"extra"` + Enabled *bool `json:"enabled,omitempty"` + RegionId string `json:"region_id"` + ServiceCertificateId string `json:"service_certificate_id"` } // SFederatedUser is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SFederatedUser. @@ -188,6 +189,14 @@ type SService struct { Extra interface{} `json:"extra"` } +// SServiceCertificate is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SServiceCertificate. +type SServiceCertificate struct { + apis.SStandaloneResourceBase + apis.SCertificateResourceBase + CaCertificate string `json:"ca_certificate"` + CaPrivateKey string `json:"ca_private_key"` +} + // SUser is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SUser. type SUser struct { SEnabledIdentityBaseResource diff --git a/pkg/apis/zz_generated.model.go b/pkg/apis/zz_generated.model.go index 233d6d57f9..2214da7c87 100644 --- a/pkg/apis/zz_generated.model.go +++ b/pkg/apis/zz_generated.model.go @@ -24,6 +24,21 @@ type SAdminSharableVirtualResourceBase struct { Records string `json:"records"` } +// SCertificateResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SCertificateResourceBase. +type SCertificateResourceBase struct { + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` + // derived attributes + PublicKeyAlgorithm string `json:"public_key_algorithm"` + PublicKeyBitLen int `json:"public_key_bit_len"` + SignatureAlgorithm string `json:"signature_algorithm"` + Fingerprint string `json:"fingerprint"` + NotBefore time.Time `json:"not_before"` + NotAfter time.Time `json:"not_after"` + CommonName string `json:"common_name"` + SubjectAlternativeNames string `json:"subject_alternative_names"` +} + // SDomainLevelResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SDomainLevelResourceBase. type SDomainLevelResourceBase struct { SStandaloneResourceBase diff --git a/pkg/cloudcommon/db/certificateresource.go b/pkg/cloudcommon/db/certificateresource.go new file mode 100644 index 0000000000..1d7b0ab58f --- /dev/null +++ b/pkg/cloudcommon/db/certificateresource.go @@ -0,0 +1,20 @@ +package db + +import ( + "time" +) + +type SCertificateResourceBase struct { + Certificate string `create:"required" list:"user" update:"user"` + PrivateKey string `create:"required" list:"admin" update:"user"` + + // derived attributes + PublicKeyAlgorithm string `create:"optional" list:"user" update:"user"` + PublicKeyBitLen int `create:"optional" list:"user" update:"user"` + SignatureAlgorithm string `create:"optional" list:"user" update:"user"` + Fingerprint string `create:"optional" list:"user" update:"user"` + NotBefore time.Time `create:"optional" list:"user" update:"user"` + NotAfter time.Time `create:"optional" list:"user" update:"user"` + CommonName string `create:"optional" list:"user" update:"user"` + SubjectAlternativeNames string `create:"optional" list:"user" update:"user"` +} diff --git a/pkg/cloudcommon/validators/validators_cert.go b/pkg/cloudcommon/validators/validators_cert.go index 2c097b96dc..993a7bf050 100644 --- a/pkg/cloudcommon/validators/validators_cert.go +++ b/pkg/cloudcommon/validators/validators_cert.go @@ -15,6 +15,7 @@ package validators import ( + "context" "crypto" "crypto/ecdsa" "crypto/rsa" @@ -24,8 +25,12 @@ import ( "encoding/pem" "fmt" "reflect" + "strings" "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/httperrors" ) type ValidatorPEM struct { @@ -372,3 +377,69 @@ func (v *ValidatorPrivateKey) MatchCertificate(cert *x509.Certificate) error { } return nil } + +type ValidatorCertKey struct { + *ValidatorCertificate + *ValidatorPrivateKey + + certPubKeyAlgo string +} + +func NewCertKeyValidator(cert, key string) *ValidatorCertKey { + return &ValidatorCertKey{ + ValidatorCertificate: NewCertificateValidator(cert), + ValidatorPrivateKey: NewPrivateKeyValidator(key), + } +} + +func (v *ValidatorCertKey) Validate(data *jsonutils.JSONDict) error { + keyV := map[string]IValidator{ + "certificate": v.ValidatorCertificate, + "private_key": v.ValidatorPrivateKey, + } + for _, v := range keyV { + if err := v.Validate(data); err != nil { + return err + } + } + cert := v.ValidatorCertificate.Certificates[0] + var certPubKeyAlgo string + { + // x509.PublicKeyAlgorithm.String() is only available since go1.10 + switch cert.PublicKeyAlgorithm { + case x509.RSA: + certPubKeyAlgo = api.LB_TLS_CERT_PUBKEY_ALGO_RSA + case x509.ECDSA: + certPubKeyAlgo = api.LB_TLS_CERT_PUBKEY_ALGO_ECDSA + default: + certPubKeyAlgo = fmt.Sprintf("algo %#v", cert.PublicKeyAlgorithm) + } + if !api.LB_TLS_CERT_PUBKEY_ALGOS.Has(certPubKeyAlgo) { + return httperrors.NewInputParameterError("invalid cert pubkey algorithm: %s, want %s", + certPubKeyAlgo, api.LB_TLS_CERT_PUBKEY_ALGOS.String()) + } + } + v.certPubKeyAlgo = certPubKeyAlgo + if err := v.ValidatorPrivateKey.MatchCertificate(cert); err != nil { + return err + } + return nil +} + +func (v *ValidatorCertKey) UpdateCertKeyInfo(ctx context.Context, data *jsonutils.JSONDict) *jsonutils.JSONDict { + cert := v.ValidatorCertificate.Certificates[0] + // NOTE subject alternative names also includes email, url, ip addresses, + // but we ignore them here. + // + // NOTE we use white space to separate names + data.Set("common_name", jsonutils.NewString(cert.Subject.CommonName)) + data.Set("subject_alternative_names", jsonutils.NewString(strings.Join(cert.DNSNames, " "))) + + data.Set("not_before", jsonutils.NewTimeString(cert.NotBefore)) + data.Set("not_after", jsonutils.NewTimeString(cert.NotAfter)) + data.Set("public_key_algorithm", jsonutils.NewString(v.certPubKeyAlgo)) + data.Set("public_key_bit_len", jsonutils.NewInt(int64(v.ValidatorCertificate.PublicKeyBitLen()))) + data.Set("signature_algorithm", jsonutils.NewString(cert.SignatureAlgorithm.String())) + data.Set("fingerprint", jsonutils.NewString(api.LB_TLS_CERT_FINGERPRINT_ALGO_SHA256+":"+v.ValidatorCertificate.FingerprintSha256String())) + return data +} diff --git a/pkg/cloudcommon/validators/validators_cert_test.go b/pkg/cloudcommon/validators/validators_cert_test.go index 696803a3d6..c0c6e804d9 100644 --- a/pkg/cloudcommon/validators/validators_cert_test.go +++ b/pkg/cloudcommon/validators/validators_cert_test.go @@ -13,3 +13,109 @@ // limitations under the License. package validators + +import ( + "testing" + + "yunion.io/x/jsonutils" +) + +func TestValidateCertKey_Validate(t *testing.T) { + type fields struct { + ValidatorCertificate *ValidatorCertificate + ValidatorPrivateKey *ValidatorPrivateKey + certPubKeyAlgo string + } + type args struct { + data *jsonutils.JSONDict + } + cert := `-----BEGIN CERTIFICATE----- +MIIDEDCCAfigAwIBAgIIPfkszEMuuikwDQYJKoZIhvcNAQELBQAwEzERMA8GA1UE +AxMIb25lY2xvdWQwHhcNMjAwMTAzMTEwMzA3WhcNMjIwMzEzMTEwMzA3WjASMRAw +DgYDVQQDEwdzZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +phzvJJ3grosTiPBmmip6OpLkOezhbyuYbDGhynDH9N3+BWOQsPjzkigMhQjoygzH +G3Ac4f6yUJEzThTlkEckEVvxKftXTX+u2/F4SnaF61wiLA8SD2nXtYFNUhaOQ9Bo +vIcUlI/6idcxUZiktbAHv9FnawfNk1n6ryEy8LXPWS5RP+GhQfQG0Zko9APvLr/4 +OB9zqgzc8+LUppxYtLMoy42FJ/OZhnGo31BWB9RW2WML+oO6J+uxt0JQGJwp8vr0 +8OpSp5rgbu8G5jDlCOj4hEctbflJus/ES2jaR0etOcVSt+GYeTrKPmX9pPZ4TS9A +C1Lok0RR1xiDTS7Gw6pGnwIDAQABo2kwZzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0l +BAwwCgYIKwYBBQUHAwEwQAYDVR0RBDkwN4IHc2VydmljZYIQc2VydmljZS5vbmVj +bG91ZIIUc2VydmljZS5vbmVjbG91ZC5zdmOHBAqo3pYwDQYJKoZIhvcNAQELBQAD +ggEBAHYS4p2UrJ977SYFYYpsrE6Q01XSG6qt9EDTT9iB5GA/viuLURVHUMQDxKnf +2hSMq/UV+pGfGBw0Ki2sd+Mylb9qi59c26Ogqe8N/v+c219bYdnN7IzAgLQsEOd8 +3iEJ7Ypb5pgf3B/dBPyWzxmKjZQ7vIfLYWgbmigPtf29yCWd3AlZrhI9zQUEcq7D +EAqtcvpU5/y7QsBNXo0QJa1WeeAOzYnKHUPQBJU4qLPm305BDHhKFyY23jsRSQMO +3CTjx9YsN9qGnv+oaqleA/ua/4f0QoPEQMXUsN1FGsAsC+vcKwDodY6vIvFnAk6o +pYTlCl+Ls6/Bu/Oml8AvrlaEyuc= +-----END CERTIFICATE-----` + pkey := `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAphzvJJ3grosTiPBmmip6OpLkOezhbyuYbDGhynDH9N3+BWOQ +sPjzkigMhQjoygzHG3Ac4f6yUJEzThTlkEckEVvxKftXTX+u2/F4SnaF61wiLA8S +D2nXtYFNUhaOQ9BovIcUlI/6idcxUZiktbAHv9FnawfNk1n6ryEy8LXPWS5RP+Gh +QfQG0Zko9APvLr/4OB9zqgzc8+LUppxYtLMoy42FJ/OZhnGo31BWB9RW2WML+oO6 +J+uxt0JQGJwp8vr08OpSp5rgbu8G5jDlCOj4hEctbflJus/ES2jaR0etOcVSt+GY +eTrKPmX9pPZ4TS9AC1Lok0RR1xiDTS7Gw6pGnwIDAQABAoIBAHqbvrQDSBTtGIUq +FEFUexWC2KwcuSSqQ/4QAECBUEXgGR/3JpRJnNbTcrI7KkAAgHIzJU52BT3Mftby +O6NrryaU+4OmPgE47mLvb39ezmgzgBGPKiBwWkRhZSXi+iz5xmTpO3qQbzeQu5lj +lqd4f6/Iq5Hnl4hckNj1IzliqOJEShiYQV69NADbUsnmruHL3Mt3f3SQI9yv+k48 +5QMGol0eIuFkur6+I0mryvfx36gqHOsZIRweAMPGmGLr+aatrsXfV1pHON/4FhPY +dOSEpVz2hOPt9HMMy8AJXjMDvl8RfaW/T7NY+2lJoE4IMcxuGOqQRGnnBRioOuP9 +lnuTbnECgYEAxq79lGJb5oasmk277Z+8h7BF9lHMDo6jah8cEGL5lTS4u7SYcLHp +uL9WBDoAhdVX2XULF13D+NPXQObySr96oGXJxlAtxSpRjURAGlLIn4Y3rth+RUT/ +k+N7QkS78wfUCf71icnXr71SqctVC6tJnqgvxF5JP+Cy4P1BuSYcPEkCgYEA1giS +f17Q3t1iWmNGLRb+J6JQeYoQyFDPX/ROz0Ko7Nf1svzYLTWrjJGjXXw8/vUOs0pP +M7yV2TFzLT7nGuDBfwFVU4Kv3JNU5x+Z6++g7tv3/3JiKG0QXQvwNPbYVHrWj1cf +T15HyVNZwibydEl0Q6HdZpzZP2cxg3Jm9kVJW6cCgYAdOMmVFG5d1nr2au50AaVx +84wmsVso3PPN/OtcwaHhvxJYkTRGhvRQNtwI3RsMlBdKpXtPIXxcUZP8OLt0IPuB +MddecpZ4xEOgWmRvOrPFOrFf5vmTaJWKg8+yLHfUQ9d87OHiNSyi7V6GGKDWiYfX +bPcxk4iEe6DzlGwhNii6+QKBgQDF38T4poL6F7gvEmq1kvVDVSeLRd6AI12lO2uE +5/7egEXxxRpiqaTA34AmFI8bsxl1HjUdArOSycnOwcHNMo8RSP1GqKLHjRpIVwnp +e2/QhGLBslEXSMWBEGFxxeh4KdylRol2yhYaBcoM2g76/VHUmRfkHwwmNtQqzyBr +e+D3LwKBgE2fGOe5rqZ8/mCJcx7Wlt8gqD66WUhGqMh3pd4Om6ZGFVLvhPWEu3Md +57oFd4cQZ6FSIOKb+cFIpCIW8sXG+c3vFjIC5PDMnSPbasF5KHN/Kg3C+p2ilviv +Oycn3Dy2jpdE7SpoBCt3HsIhra8a6h7BcCQ87UwObqTsdLe+7/oD +-----END RSA PRIVATE KEY-----` + var data = jsonutils.NewDict() + data.Set("certificate", jsonutils.NewString(cert)) + data.Set("private_key", jsonutils.NewString(pkey)) + var data2 = jsonutils.NewDict() + data2.Set("certificate", jsonutils.NewString(cert+"error")) + data2.Set("private_key", jsonutils.NewString(pkey+"error")) + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "good in", + fields: fields{ + ValidatorCertificate: NewCertificateValidator("certificate"), + ValidatorPrivateKey: NewPrivateKeyValidator("private_key"), + }, + args: args{data}, + wantErr: false, + }, + { + name: "bad in", + fields: fields{ + ValidatorCertificate: NewCertificateValidator("certificate"), + ValidatorPrivateKey: NewPrivateKeyValidator("private_key"), + }, + args: args{data2}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &ValidatorCertKey{ + ValidatorCertificate: tt.fields.ValidatorCertificate, + ValidatorPrivateKey: tt.fields.ValidatorPrivateKey, + certPubKeyAlgo: tt.fields.certPubKeyAlgo, + } + if err := v.Validate(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("ValidateCertKey.Validator() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/compute/models/loadbalancercertificates.go b/pkg/compute/models/loadbalancercertificates.go index 8010408fa4..b47f9987d7 100644 --- a/pkg/compute/models/loadbalancercertificates.go +++ b/pkg/compute/models/loadbalancercertificates.go @@ -22,8 +22,6 @@ import ( "encoding/hex" "encoding/pem" "fmt" - "strings" - "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -70,18 +68,7 @@ type SLoadbalancerCertificate struct { // SManagedResourceBase // SCloudregionResourceBase - Certificate string `create:"required" list:"user" update:"user"` - PrivateKey string `create:"required" list:"admin" update:"user"` - - // derived attributes - PublicKeyAlgorithm string `create:"optional" list:"user" update:"user"` - PublicKeyBitLen int `create:"optional" list:"user" update:"user"` - SignatureAlgorithm string `create:"optional" list:"user" update:"user"` - Fingerprint string `create:"optional" list:"user" update:"user"` - NotBefore time.Time `create:"optional" list:"user" update:"user"` - NotAfter time.Time `create:"optional" list:"user" update:"user"` - CommonName string `create:"optional" list:"user" update:"user"` - SubjectAlternativeNames string `create:"optional" list:"user" update:"user"` + db.SCertificateResourceBase } func (lbcert *SLoadbalancerCertificate) GetCachedCerts() ([]SCachedLoadbalancerCertificate, error) { @@ -201,55 +188,6 @@ func (lbcert *SLoadbalancerCertificate) Delete(ctx context.Context, userCred mcc return nil } -func (man *SLoadbalancerCertificateManager) validateCertKey(ctx context.Context, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - certV := validators.NewCertificateValidator("certificate") - pkeyV := validators.NewPrivateKeyValidator("private_key") - keyV := map[string]validators.IValidator{ - "certificate": certV, - "private_key": pkeyV, - } - for _, v := range keyV { - if err := v.Validate(data); err != nil { - return nil, err - } - } - cert := certV.Certificates[0] - var certPubKeyAlgo string - { - // x509.PublicKeyAlgorithm.String() is only available since go1.10 - switch cert.PublicKeyAlgorithm { - case x509.RSA: - certPubKeyAlgo = api.LB_TLS_CERT_PUBKEY_ALGO_RSA - case x509.ECDSA: - certPubKeyAlgo = api.LB_TLS_CERT_PUBKEY_ALGO_ECDSA - default: - certPubKeyAlgo = fmt.Sprintf("algo %#v", cert.PublicKeyAlgorithm) - } - if !api.LB_TLS_CERT_PUBKEY_ALGOS.Has(certPubKeyAlgo) { - return nil, httperrors.NewInputParameterError("invalid cert pubkey algorithm: %s, want %s", - certPubKeyAlgo, api.LB_TLS_CERT_PUBKEY_ALGOS.String()) - } - } - err := pkeyV.MatchCertificate(cert) - if err != nil { - return nil, err - } - // NOTE subject alternative names also includes email, url, ip addresses, - // but we ignore them here. - // - // NOTE we use white space to separate names - data.Set("common_name", jsonutils.NewString(cert.Subject.CommonName)) - data.Set("subject_alternative_names", jsonutils.NewString(strings.Join(cert.DNSNames, " "))) - - data.Set("not_before", jsonutils.NewTimeString(cert.NotBefore)) - data.Set("not_after", jsonutils.NewTimeString(cert.NotAfter)) - data.Set("public_key_algorithm", jsonutils.NewString(certPubKeyAlgo)) - data.Set("public_key_bit_len", jsonutils.NewInt(int64(certV.PublicKeyBitLen()))) - data.Set("signature_algorithm", jsonutils.NewString(cert.SignatureAlgorithm.String())) - data.Set("fingerprint", jsonutils.NewString(api.LB_TLS_CERT_FINGERPRINT_ALGO_SHA256+":"+certV.FingerprintSha256String())) - return data, nil -} - func (man *SLoadbalancerCertificateManager) ListItemFilter( ctx context.Context, q *sqlchemy.SQuery, @@ -335,13 +273,14 @@ func (man *SLoadbalancerCertificateManager) QueryDistinctExtraField(q *sqlchemy. } func (man *SLoadbalancerCertificateManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - data, err := man.validateCertKey(ctx, data) - if err != nil { + v := validators.NewCertKeyValidator("certificate", "private_key") + if err := v.Validate(data); err != nil { return nil, err } + data = v.UpdateCertKeyInfo(ctx, data) input := apis.VirtualResourceCreateInput{} - err = data.Unmarshal(&input) + err := data.Unmarshal(&input) if err != nil { return nil, httperrors.NewInternalServerError("unmarshal VirtualResourceCreateInput fail %s", err) } diff --git a/pkg/keystone/models/endpoints.go b/pkg/keystone/models/endpoints.go index 9b55133cc5..9e41e48308 100644 --- a/pkg/keystone/models/endpoints.go +++ b/pkg/keystone/models/endpoints.go @@ -73,13 +73,14 @@ func init() { type SEndpoint struct { db.SStandaloneResourceBase - LegacyEndpointId string `width:"64" charset:"ascii" nullable:"true"` - Interface string `width:"8" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` - ServiceId string `width:"64" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` - Url string `charset:"utf8" nullable:"false" list:"admin" update:"admin" create:"admin_required"` - Extra *jsonutils.JSONDict `nullable:"true"` - Enabled tristate.TriState `nullable:"false" default:"true" list:"admin" update:"admin" create:"admin_optional"` - RegionId string `width:"255" charset:"utf8" nullable:"true" list:"admin" create:"admin_required"` + LegacyEndpointId string `width:"64" charset:"ascii" nullable:"true"` + Interface string `width:"8" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + ServiceId string `width:"64" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + Url string `charset:"utf8" nullable:"false" list:"admin" update:"admin" create:"admin_required"` + Extra *jsonutils.JSONDict `nullable:"true"` + Enabled tristate.TriState `nullable:"false" default:"true" list:"admin" update:"admin" create:"admin_optional"` + RegionId string `width:"255" charset:"utf8" nullable:"true" list:"admin" create:"admin_required"` + ServiceCertificateId string `nullable:"true" create:"admin_optional" update:"admin"` } func (manager *SEndpointManager) InitializeData() error { @@ -245,7 +246,23 @@ func (endpoint *SEndpoint) GetExtraDetails( query jsonutils.JSONObject, isList bool, ) (api.EndpointDetails, error) { - return api.EndpointDetails{}, nil + res, err := endpoint.getMoreDetails(api.EndpointDetails{}) + if err != nil { + return api.EndpointDetails{}, err + } + return res, nil +} + +func (endpoint *SEndpoint) getMoreDetails(details api.EndpointDetails) (api.EndpointDetails, error) { + if len(endpoint.ServiceCertificateId) > 0 { + icert, _ := ServiceCertificateManager.FetchById(endpoint.ServiceCertificateId) + if icert != nil { + cert := icert.(*SServiceCertificate) + certOutput := cert.ToOutput() + details.CertificateDetails = *certOutput + } + } + return details, nil } func (manager *SEndpointManager) FetchCustomizeColumns( @@ -266,6 +283,8 @@ func (manager *SEndpointManager) FetchCustomizeColumns( } ep := objs[i].(*SEndpoint) serviceIds = stringutils2.Append(serviceIds, ep.ServiceId) + ep.SetModelManager(manager, ep) + rows[i], _ = ep.getMoreDetails(rows[i]) } if len(fields) == 0 || fields.Contains("service_name") || fields.Contains("service_type") { svs := fetchServices(serviceIds) @@ -307,7 +326,10 @@ func (endpoint *SEndpoint) ValidateDeleteCondition(ctx context.Context) error { return endpoint.SStandaloneResourceBase.ValidateDeleteCondition(ctx) } -func (manager *SEndpointManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { +func (manager *SEndpointManager) ValidateCreateData( + ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, data *jsonutils.JSONDict, +) (*jsonutils.JSONDict, error) { infname, _ := data.GetString("interface") if len(infname) == 0 { return nil, httperrors.NewInputParameterError("missing input field interface") @@ -330,6 +352,17 @@ func (manager *SEndpointManager) ValidateCreateData(ctx context.Context, userCre } else { return nil, httperrors.NewInputParameterError("missing input field service/service_id") } + if certId, _ := data.GetString("service_certificate"); len(certId) > 0 { + cert, err := ServiceCertificateManager.FetchByIdOrName(userCred, certId) + if err == sql.ErrNoRows { + return nil, httperrors.NewNotFoundError("not found cert %s", certId) + } + if err != nil { + return nil, err + } + data.Set("service_certificate_id", jsonutils.NewString(cert.GetId())) + } + input := apis.StandaloneResourceCreateInput{} err := data.Unmarshal(&input) if err != nil { @@ -433,3 +466,20 @@ func (endpoint *SEndpoint) PostDelete(ctx context.Context, userCred mcclient.Tok logclient.AddActionLogWithContext(ctx, endpoint, logclient.ACT_DELETE, nil, userCred, true) refreshDefaultClientServiceCatalog() } + +func (endpoint *SEndpoint) ValidateUpdateData( + ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data *jsonutils.JSONDict, +) (*jsonutils.JSONDict, error) { + if certId, _ := data.GetString("service_certificate"); len(certId) > 0 { + cert, err := ServiceCertificateManager.FetchByIdOrName(userCred, certId) + if err == sql.ErrNoRows { + return nil, httperrors.NewNotFoundError("not found cert %s", certId) + } + if err != nil { + return nil, err + } + data.Set("service_certificate_id", jsonutils.NewString(cert.GetId())) + } + return data, nil +} diff --git a/pkg/keystone/models/servicecertificates.go b/pkg/keystone/models/servicecertificates.go new file mode 100644 index 0000000000..cebd60c433 --- /dev/null +++ b/pkg/keystone/models/servicecertificates.go @@ -0,0 +1,128 @@ +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/apis" + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SServiceCertificateManager struct { + db.SStandaloneResourceBaseManager +} + +var ServiceCertificateManager *SServiceCertificateManager + +func init() { + ServiceCertificateManager = &SServiceCertificateManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SServiceCertificate{}, + "servicecertificates_tbl", + "servicecertificate", + "servicecertificates", + ), + } + ServiceCertificateManager.SetVirtualObject(ServiceCertificateManager) +} + +type SServiceCertificate struct { + db.SStandaloneResourceBase + db.SCertificateResourceBase + + CaCertificate string `create:"optional" list:"admin"` + CaPrivateKey string `create:"optional" list:"admin"` +} + +func (man *SServiceCertificateManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + v := validators.NewCertKeyValidator("certificate", "private_key") + if err := v.Validate(data); err != nil { + return nil, err + } + data = v.UpdateCertKeyInfo(ctx, data) + + // validate ca cert key + v = validators.NewCertKeyValidator("ca_certificate", "ca_private_key") + if err := v.Validate(data); err != nil { + return nil, err + } + + input := apis.StandaloneResourceCreateInput{} + err := data.Unmarshal(&input) + if err != nil { + return nil, httperrors.NewInternalServerError("unmarshal StandaloneResourceCreateInput fail %s", err) + } + input, err = man.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input) + if err != nil { + return nil, err + } + data.Update(jsonutils.Marshal(input)) + return data, nil +} + +func (cert *SServiceCertificate) ValidateUpdateData( + ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, data *jsonutils.JSONDict, +) (*jsonutils.JSONDict, error) { + v := validators.NewCertKeyValidator("certificate", "private_key") + if err := v.Validate(data); err != nil { + return nil, err + } + data = v.UpdateCertKeyInfo(ctx, data) + + // validate ca cert key + v = validators.NewCertKeyValidator("ca_certificate", "ca_private_key") + if err := v.Validate(data); err != nil { + return nil, err + } + + updateData := jsonutils.NewDict() + if name, err := data.GetString("name"); err == nil { + updateData.Set("name", jsonutils.NewString(name)) + } + + if desc, err := data.GetString("description"); err == nil { + updateData.Set("description", jsonutils.NewString(desc)) + } + + input := apis.StandaloneResourceBaseUpdateInput{} + err := updateData.Unmarshal(&input) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + input, err = cert.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, input) + if err != nil { + return nil, errors.Wrap(err, "SVirtualResourceBase.ValidateUpdateData") + } + updateData.Update(jsonutils.Marshal(input)) + + return updateData, nil +} + +func (cert *SServiceCertificate) ToOutput() *api.CertificateDetails { + return &api.CertificateDetails{ + SCertificateResourceBase: apis.SCertificateResourceBase{ + Certificate: cert.Certificate, + PrivateKey: cert.PrivateKey, + PublicKeyAlgorithm: cert.PublicKeyAlgorithm, + PublicKeyBitLen: cert.PublicKeyBitLen, + SignatureAlgorithm: cert.SignatureAlgorithm, + Fingerprint: cert.Fingerprint, + NotAfter: cert.NotAfter, + NotBefore: cert.NotBefore, + CommonName: cert.CommonName, + SubjectAlternativeNames: cert.SubjectAlternativeNames, + }, + CertName: cert.Name, + CertId: cert.Id, + CaCertificate: cert.CaCertificate, + CaPrivateKey: cert.CaPrivateKey, + } + +} diff --git a/pkg/keystone/service/handlers.go b/pkg/keystone/service/handlers.go index 96726ae273..36c0863d65 100644 --- a/pkg/keystone/service/handlers.go +++ b/pkg/keystone/service/handlers.go @@ -88,6 +88,7 @@ func InitHandlers(app *appsrv.Application) { models.PolicyManager, models.CredentialManager, models.IdentityProviderManager, + models.ServiceCertificateManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/mcclient/modules/mod_service_certficates.go b/pkg/mcclient/modules/mod_service_certficates.go new file mode 100644 index 0000000000..2ada20af3a --- /dev/null +++ b/pkg/mcclient/modules/mod_service_certficates.go @@ -0,0 +1,26 @@ +package modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + ServiceCertificatesV3 modulebase.ResourceManager +) + +func init() { + ServiceCertificatesV3 = NewIdentityV3Manager( + "servicecertificate", "servicecertificates", + []string{ + "id", + "name", + "algorithm", + "fingerprint", + "not_before", + "not_after", + "common_name", + "subject_alternative_names", + }, + []string{"tenant"}, + ) + + register(&ServiceCertificatesV3) +} diff --git a/pkg/mcclient/options/servicecertificates.go b/pkg/mcclient/options/servicecertificates.go new file mode 100644 index 0000000000..5988ce1a03 --- /dev/null +++ b/pkg/mcclient/options/servicecertificates.go @@ -0,0 +1,66 @@ +package options + +import ( + "fmt" + "io/ioutil" + + "yunion.io/x/jsonutils" +) + +type ServiceCertificateCreateOptions struct { + NAME string + + Cert string `required:"true" json:"-" help:"path to certificate file"` + Pkey string `required:"true" json:"-" help:"path to private key file"` + CaCert string `help:"path to ca certificate file" json:"-"` + CaPkey string `help:"paht to ca private key file" json:"-"` +} + +func serviceCertificateLoadFiles(pathM map[string]string, allowEmpty bool) (*jsonutils.JSONDict, error) { + params := jsonutils.NewDict() + for fieldName, path := range pathM { + if path == "" { + if allowEmpty { + continue + } else { + return nil, fmt.Errorf("%s: empty path", fieldName) + } + } + d, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("%s: read %s: %s", fieldName, path, err) + } + if len(d) == 0 { + return nil, fmt.Errorf("%s: empty file %s", fieldName, path) + } + params.Set(fieldName, jsonutils.NewString(string(d))) + } + return params, nil +} + +func (opts *ServiceCertificateCreateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := StructToParams(opts) + if err != nil { + return nil, err + } + pathM := map[string]string{ + "certificate": opts.Cert, + "private_key": opts.Pkey, + } + paramsCertKey, err := serviceCertificateLoadFiles(pathM, false) + if err != nil { + return nil, err + } + params.Update(paramsCertKey) + + pathM = map[string]string{ + "ca_certificate": opts.CaCert, + "ca_private_key": opts.CaPkey, + } + paramsCertKey, err = serviceCertificateLoadFiles(pathM, false) + if err != nil { + return nil, err + } + params.Update(paramsCertKey) + return params, nil +}