From c77e2e5922e81166a8920752a569d541dfec46e0 Mon Sep 17 00:00:00 2001 From: Michael Henkel Date: Mon, 16 Dec 2019 11:42:38 -0800 Subject: [PATCH 1/6] Adds TLS support for VNC API communication Closes-jira-task: CEM-11391 --- client.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/client.go b/client.go index baa5dc0..8ca35f4 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,8 @@ package contrail import ( "bytes" + "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "io" @@ -49,6 +51,53 @@ func (*NopAuthenticator) AddAuthentication(*http.Request) error { return nil } +// The Encryptor interface is used to add an encryption to the REST call +type Encryptor interface { + AddEncryption(caFile string, keyFile string, certFile string, insecure bool) error +} + +// NopEncryptor doesn't add encryption +type NopEncryptor struct { +} + +// AddEncryption implements the Encryptor interface for NopEncryptor. +func (*NopEncryptor) AddEncryption(caFile string, keyFile string, certFile string, insecure bool) error { + return nil +} + +// AddEncryption implements the Encryptor interface for Client. +func (c *Client) AddEncryption(caFile string, keyFile string, certFile string, insecure bool) error { + c.scheme = "https" + tlsConfig := &tls.Config{} + if insecure { + tlsConfig.InsecureSkipVerify = true + } else if caFile != "" { + caCert, err := ioutil.ReadFile(caFile) + if err != nil { + return nil + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig.RootCAs = caCertPool + if certFile != "" && keyFile != "" { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil + } + tlsConfig.Certificates = []tls.Certificate{cert} + } else { + tlsConfig.InsecureSkipVerify = true + } + + } + transport := &http.Transport{ + TLSClientConfig: tlsConfig, + } + c.httpClient.Transport = transport + + return nil +} + // ApiClient interface type ApiClient interface { Create(ptr IObject) error @@ -68,9 +117,17 @@ type ApiClient interface { // A Client of the OpenContrail API server. type Client struct { server string + scheme string port int httpClient *http.Client auth Authenticator + encrypt Encryptor +} + +type TlsConfig struct { + ca string + key string + cert string } // ListResult is the return type of the {List, ListByParent} API calls. @@ -90,8 +147,10 @@ func NewClient(server string, port int) *Client { client := new(Client) client.server = server client.port = port + client.scheme = "http" client.httpClient = &http.Client{} client.auth = new(NopAuthenticator) + client.encrypt = new(NopEncryptor) return client } @@ -106,6 +165,11 @@ func (c *Client) SetAuthenticator(auth Authenticator) { c.auth = auth } +// SetEncryptor enables the user to encrypt the API traffic +func (c *Client) SetEncryptor(encrypt Encryptor) { + c.encrypt = encrypt +} + func typename(ptr IObject) string { name := reflect.TypeOf(ptr).Elem().Name() var buf []rune @@ -179,7 +243,7 @@ func (c *Client) httpDelete(url string) (*http.Response, error) { // The object must have been initialized with a name. func (c *Client) Create(ptr IObject) error { xtype := typename(ptr) - url := fmt.Sprintf("http://%s:%d/%ss", c.server, c.port, xtype) + url := fmt.Sprintf("%s://%s:%d/%ss", c.scheme, c.server, c.port, xtype) objJson, err := json.Marshal(ptr) if err != nil { @@ -319,8 +383,7 @@ func (c *Client) Update(ptr IObject) error { // DeleteByUuid deletes the specified object. func (c *Client) DeleteByUuid(typename, uuid string) error { - url := fmt.Sprintf("http://%s:%d/%s/%s", - c.server, c.port, typename, uuid) + url := fmt.Sprintf("%s://%s:%d/%s/%s", c.scheme, c.server, c.port, typename, uuid) resp, err := c.httpDelete(url) if err != nil { return err @@ -359,14 +422,14 @@ func (c *Client) Delete(ptr IObject) error { // FindByUuid reads an object identified by UUID. func (c *Client) FindByUuid(typename string, uuid string) (IObject, error) { - url := fmt.Sprintf("http://%s:%d/%s/%s", c.server, c.port, + url := fmt.Sprintf("%s://%s:%d/%s/%s", c.scheme, c.server, c.port, typename, uuid) return c.readObject(typename, url) } // UuidByName returns the UUID of an object as identified by its fully qualified name. func (c *Client) UuidByName(typename string, fqn string) (string, error) { - url := fmt.Sprintf("http://%s:%d/fqname-to-id", c.server, c.port) + url := fmt.Sprintf("%s://%s:%d/fqname-to-id", c.scheme, c.server, c.port) request := struct { Typename string `json:"type"` Fq_name []string `json:"fq_name"` @@ -415,7 +478,7 @@ func (c *Client) FQNameByUuid(uuid string) ([]string, error) { if err != nil { return nil, err } - url := fmt.Sprintf("http://%s:%d/id-to-fqname", c.server, c.port) + url := fmt.Sprintf("%s://%s:%d/id-to-fqname", c.scheme, c.server, c.port) resp, err := c.httpPost(url, "application/json", bytes.NewReader(data)) if err != nil { return nil, err @@ -446,7 +509,7 @@ func (c *Client) FindByName(typename string, fqn string) (IObject, error) { return nil, err } href := fmt.Sprintf( - "http://%s:%d/%s/%s", c.server, c.port, typename, uuid) + "%s://%s:%d/%s/%s", c.scheme, c.server, c.port, typename, uuid) return c.readObject(typename, href) } @@ -460,7 +523,7 @@ func (c *Client) ListByParent( values.Add("parent_id", parentID) } - url := fmt.Sprintf("http://%s:%d/%ss", c.server, c.port, typename) + url := fmt.Sprintf("%s://%s:%d/%ss", c.scheme, c.server, c.port, typename) if len(values) > 0 { url += fmt.Sprintf("?%s", values.Encode()) } @@ -514,8 +577,7 @@ func (c *Client) ListDetailByParent( } values.Add("detail", "true") - url := fmt.Sprintf("http://%s:%d/%ss?%s", - c.server, c.port, typename, values.Encode()) + url := fmt.Sprintf("%s://%s:%d/%ss?%s", c.scheme, c.server, c.port, typename, values.Encode()) resp, err := c.httpGet(url) if err != nil { return nil, err @@ -618,7 +680,7 @@ func (c *Client) UpdateReference(msg *ReferenceUpdateMsg) error { if err != nil { return err } - url := fmt.Sprintf("http://%s:%d/ref-update", c.server, c.port) + url := fmt.Sprintf("%s://%s:%d/ref-update", c.scheme, c.server, c.port) resp, err := c.httpPost(url, "application/json", bytes.NewReader(data)) if err != nil { return err From 17b5b357c271af79fb6d518c90c43e1874514d72 Mon Sep 17 00:00:00 2001 From: "shifali.choubey" Date: Mon, 23 Mar 2020 16:31:57 +0100 Subject: [PATCH 2/6] chnages the request header for v3 --- keystone.go | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/keystone.go b/keystone.go index b4dfe4e..a1a1dd5 100644 --- a/keystone.go +++ b/keystone.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/json" "fmt" + "strings" "time" "io/ioutil" "net/http" @@ -78,7 +79,7 @@ func (kClient *KeystoneClient) Authenticate() error { } `json:"token"` } `json:"auth"` } - type AuthCredentialsRequest struct { + type AuthCredentialsRequestv2 struct { Auth struct { TenantName string `json:"tenantName"` PasswordCredentials struct { @@ -87,6 +88,25 @@ func (kClient *KeystoneClient) Authenticate() error { } `json:"passwordCredentials"` } `json:"auth"` } + + //Credential request for v3 schema + type AuthCredentialsRequestv3 struct { + Auth struct { + Identity struct { + Methods []string `json:"methods"` + Password struct { + User struct { + Domain struct { + ID string `json:"id"` + } `json:"domain"` + Name string `json:"name"` + Password string `json:"password"` + } `json:"user"` + } `json:"password"` + } `json:"identity"` + } `json:"auth"` + } + // identity-api/v2.0/src/xsd/token.xsd // type TokenResponse struct { @@ -105,6 +125,9 @@ func (kClient *KeystoneClient) Authenticate() error { } url += "tokens" + parts := strings.Split(url, "/") + schemaversion := parts[3] + var data []byte var err error if len(kClient.osAdminToken) > 0 { @@ -112,14 +135,24 @@ func (kClient *KeystoneClient) Authenticate() error { request.Auth.Token.Id = kClient.osAdminToken data, err = json.Marshal(&request) } else { - request := AuthCredentialsRequest{} - request.Auth.PasswordCredentials.Username = - kClient.osUsername - request.Auth.PasswordCredentials.Password = - kClient.osPassword - request.Auth.TenantName = kClient.osTenantName - data, err = json.Marshal(&request) + if schemaversion == "v3" { + request := AuthCredentialsRequestv3{} + request.Auth.Identity.Password.User.Name = kClient.osUsername + request.Auth.Identity.Password.User.Password = kClient.osPassword + request.Auth.Identity.Password.User.Domain.ID = "default" + request.Auth.Identity.Methods = append(request.Auth.Identity.Methods, "password") + data, err = json.Marshal(&request) + } else { + request := AuthCredentialsRequestv2{} + request.Auth.PasswordCredentials.Username = + kClient.osUsername + request.Auth.PasswordCredentials.Password = + kClient.osPassword + request.Auth.TenantName = kClient.osTenantName + data, err = json.Marshal(&request) + } } + if err != nil { return err } @@ -138,7 +171,7 @@ func (kClient *KeystoneClient) Authenticate() error { return err } - if resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated{ return fmt.Errorf("%s: %s", resp.Status, body) } From 6b07b5c335c6d561a6fb80224edd1989e94ff485 Mon Sep 17 00:00:00 2001 From: "shifali.choubey" Date: Wed, 25 Mar 2020 16:52:57 +0100 Subject: [PATCH 3/6] changed response structure for v3 schema --- keystone.go | 205 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 149 insertions(+), 56 deletions(-) diff --git a/keystone.go b/keystone.go index a1a1dd5..2bf2df9 100644 --- a/keystone.go +++ b/keystone.go @@ -8,10 +8,9 @@ import ( "bytes" "encoding/json" "fmt" - "strings" - "time" "io/ioutil" "net/http" + "time" ) // KeystoneClient is a client of the OpenStack Keystone service that adds authentication @@ -23,7 +22,10 @@ type KeystoneClient struct { osPassword string osAdminToken string - current *KeystoneToken + tokenID string + expiresAt string + issuedAt string + isv3Client bool } type KeepaliveKeystoneClient struct { @@ -44,6 +46,43 @@ type KeystoneToken struct { Issued_At string } +type KeystoneTokenv3 struct { + Token struct { + Methods []string `json:"methods"` + Roles []struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"roles"` + System struct { + All bool `json:"all"` + } `json:"system"` + ExpiresAt time.Time `json:"expires_at"` + Catalog []struct { + Endpoints []struct { + RegionID string `json:"region_id"` + URL string `json:"url"` + Region string `json:"region"` + Interface string `json:"interface"` + ID string `json:"id"` + } `json:"endpoints"` + Type string `json:"type"` + ID string `json:"id"` + Name string `json:"name"` + } `json:"catalog"` + User struct { + PasswordExpiresAt interface{} `json:"password_expires_at"` + Domain struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` + } `json:"user"` + AuditIds []string `json:"audit_ids"` + IssuedAt time.Time `json:"issued_at"` + } `json:"token"` +} + // NewKeystoneClient allocates and initializes a KeystoneClient func NewKeystoneClient(auth_url, tenant_name, username, password, token string) *KeystoneClient { return &KeystoneClient{ @@ -52,23 +91,101 @@ func NewKeystoneClient(auth_url, tenant_name, username, password, token string) username, password, token, - nil, + "", + "", + "", + false, } } func NewKeepaliveKeystoneClient(auth_url, tenant_name, username, password, token string) *KeepaliveKeystoneClient { - return &KeepaliveKeystoneClient { + return &KeepaliveKeystoneClient{ KeystoneClient{ auth_url, tenant_name, username, password, token, - nil, + "", + "", + "", + false, }, } } +// Authenticate sends an authentication request to keystone. +func (kClient *KeystoneClient) AuthenticateV3() error { + kClient.isv3Client = true + type AuthCredentialsRequestv3 struct { + Auth struct { + Identity struct { + Methods []string `json:"methods"` + Password struct { + User struct { + Domain struct { + ID string `json:"id"` + } `json:"domain"` + Name string `json:"name"` + Password string `json:"password"` + } `json:"user"` + } `json:"password"` + } `json:"identity"` + Scope struct { + System struct { + All bool `json:"all"` + } `json:"system"` + } `json:"scope"` + } `json:"auth"` + } + + url := kClient.osAuthURL + if url[len(url)-1] != '/' { + url += "/" + } + url += "tokens" + + var data []byte + var err error + request := AuthCredentialsRequestv3{} + request.Auth.Identity.Password.User.Name = kClient.osUsername + request.Auth.Identity.Password.User.Password = kClient.osPassword + request.Auth.Identity.Password.User.Domain.ID = "default" + request.Auth.Identity.Methods = append(request.Auth.Identity.Methods, "password") + request.Auth.Scope.System.All = true + if data, err = json.Marshal(&request); err != nil { + return err + } + + resp, err := http.Post(url, "application/json", + bytes.NewReader(data)) + + if err != nil { + return err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + return fmt.Errorf("%s: %s", resp.Status, body) + } + + var response KeystoneTokenv3 + err = json.Unmarshal(body, &response) + if err != nil { + return err + } + kClient.tokenID = resp.Header.Get("X-Subject-Token") + kClient.issuedAt = response.Token.IssuedAt.String() + kClient.expiresAt = response.Token.ExpiresAt.String() + return nil + +} + // Authenticate sends an authentication request to keystone. func (kClient *KeystoneClient) Authenticate() error { // identity:CredentialType @@ -79,7 +196,7 @@ func (kClient *KeystoneClient) Authenticate() error { } `json:"token"` } `json:"auth"` } - type AuthCredentialsRequestv2 struct { + type AuthCredentialsRequest struct { Auth struct { TenantName string `json:"tenantName"` PasswordCredentials struct { @@ -89,24 +206,6 @@ func (kClient *KeystoneClient) Authenticate() error { } `json:"auth"` } - //Credential request for v3 schema - type AuthCredentialsRequestv3 struct { - Auth struct { - Identity struct { - Methods []string `json:"methods"` - Password struct { - User struct { - Domain struct { - ID string `json:"id"` - } `json:"domain"` - Name string `json:"name"` - Password string `json:"password"` - } `json:"user"` - } `json:"password"` - } `json:"identity"` - } `json:"auth"` - } - // identity-api/v2.0/src/xsd/token.xsd // type TokenResponse struct { @@ -125,9 +224,6 @@ func (kClient *KeystoneClient) Authenticate() error { } url += "tokens" - parts := strings.Split(url, "/") - schemaversion := parts[3] - var data []byte var err error if len(kClient.osAdminToken) > 0 { @@ -135,22 +231,13 @@ func (kClient *KeystoneClient) Authenticate() error { request.Auth.Token.Id = kClient.osAdminToken data, err = json.Marshal(&request) } else { - if schemaversion == "v3" { - request := AuthCredentialsRequestv3{} - request.Auth.Identity.Password.User.Name = kClient.osUsername - request.Auth.Identity.Password.User.Password = kClient.osPassword - request.Auth.Identity.Password.User.Domain.ID = "default" - request.Auth.Identity.Methods = append(request.Auth.Identity.Methods, "password") - data, err = json.Marshal(&request) - } else { - request := AuthCredentialsRequestv2{} - request.Auth.PasswordCredentials.Username = - kClient.osUsername - request.Auth.PasswordCredentials.Password = - kClient.osPassword - request.Auth.TenantName = kClient.osTenantName - data, err = json.Marshal(&request) - } + request := AuthCredentialsRequest{} + request.Auth.PasswordCredentials.Username = + kClient.osUsername + request.Auth.PasswordCredentials.Password = + kClient.osPassword + request.Auth.TenantName = kClient.osTenantName + data, err = json.Marshal(&request) } if err != nil { @@ -171,7 +258,7 @@ func (kClient *KeystoneClient) Authenticate() error { return err } - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated{ + if resp.StatusCode != http.StatusOK { return fmt.Errorf("%s: %s", resp.Status, body) } @@ -181,22 +268,23 @@ func (kClient *KeystoneClient) Authenticate() error { return err } - kClient.current = new(KeystoneToken) - *kClient.current = response.Access.Token + kClient.expiresAt = response.Access.Token.Expires + kClient.issuedAt = response.Access.Token.Issued_At + kClient.tokenID = response.Access.Token.Id return nil } func (kClient *KeepaliveKeystoneClient) needsRefreshing() (bool, error) { - if kClient.current == nil { + if len(kClient.tokenID) == 0 { return true, nil } - issuedAtTime, err := time.Parse(time.RFC3339, kClient.current.Issued_At) + issuedAtTime, err := time.Parse(time.RFC3339, kClient.issuedAt) if err != nil { return false, err } - expires, err := time.Parse(time.RFC3339, kClient.current.Expires) + expires, err := time.Parse(time.RFC3339, kClient.expiresAt) if err != nil { return false, err } @@ -213,7 +301,7 @@ func (kClient *KeepaliveKeystoneClient) AddAuthentication(req *http.Request) err } if needsRefreshing { - kClient.current = nil + kClient.tokenID = "" } return kClient.KeystoneClient.AddAuthentication(req) @@ -221,12 +309,17 @@ func (kClient *KeepaliveKeystoneClient) AddAuthentication(req *http.Request) err // AddAuthentication adds the authentication data to the HTTP header. func (kClient *KeystoneClient) AddAuthentication(req *http.Request) error { - if kClient.current == nil { - err := kClient.Authenticate() - if err != nil { - return err + if len(kClient.tokenID) == 0 { + if kClient.isv3Client { + if err := kClient.AuthenticateV3(); err != nil { + return err + } + } else { + if err := kClient.Authenticate(); err != nil { + return err + } } } - req.Header.Set("X-Auth-Token", kClient.current.Id) + req.Header.Set("X-Auth-Token", kClient.tokenID) return nil } From eeb92793a3a58b75eb33ee10c39f50494b1f369a Mon Sep 17 00:00:00 2001 From: "shifali.choubey" Date: Thu, 26 Mar 2020 21:37:54 +0100 Subject: [PATCH 4/6] removed unwanted fields from structure --- keystone.go | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/keystone.go b/keystone.go index 2bf2df9..74ccf1b 100644 --- a/keystone.go +++ b/keystone.go @@ -48,38 +48,8 @@ type KeystoneToken struct { type KeystoneTokenv3 struct { Token struct { - Methods []string `json:"methods"` - Roles []struct { - ID string `json:"id"` - Name string `json:"name"` - } `json:"roles"` - System struct { - All bool `json:"all"` - } `json:"system"` ExpiresAt time.Time `json:"expires_at"` - Catalog []struct { - Endpoints []struct { - RegionID string `json:"region_id"` - URL string `json:"url"` - Region string `json:"region"` - Interface string `json:"interface"` - ID string `json:"id"` - } `json:"endpoints"` - Type string `json:"type"` - ID string `json:"id"` - Name string `json:"name"` - } `json:"catalog"` - User struct { - PasswordExpiresAt interface{} `json:"password_expires_at"` - Domain struct { - ID string `json:"id"` - Name string `json:"name"` - } `json:"domain"` - ID string `json:"id"` - Name string `json:"name"` - } `json:"user"` - AuditIds []string `json:"audit_ids"` - IssuedAt time.Time `json:"issued_at"` + IssuedAt time.Time `json:"issued_at"` } `json:"token"` } @@ -275,7 +245,7 @@ func (kClient *KeystoneClient) Authenticate() error { } func (kClient *KeepaliveKeystoneClient) needsRefreshing() (bool, error) { - if len(kClient.tokenID) == 0 { + if kClient.tokenID == "" { return true, nil } @@ -309,7 +279,7 @@ func (kClient *KeepaliveKeystoneClient) AddAuthentication(req *http.Request) err // AddAuthentication adds the authentication data to the HTTP header. func (kClient *KeystoneClient) AddAuthentication(req *http.Request) error { - if len(kClient.tokenID) == 0 { + if kClient.tokenID == "" { if kClient.isv3Client { if err := kClient.AuthenticateV3(); err != nil { return err From b74ac3331800cddb424e4d934640799e87631b2b Mon Sep 17 00:00:00 2001 From: "shifali.choubey" Date: Fri, 27 Mar 2020 18:25:52 +0100 Subject: [PATCH 5/6] chnaged the time field to string --- keystone.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/keystone.go b/keystone.go index 74ccf1b..e9bf602 100644 --- a/keystone.go +++ b/keystone.go @@ -48,8 +48,8 @@ type KeystoneToken struct { type KeystoneTokenv3 struct { Token struct { - ExpiresAt time.Time `json:"expires_at"` - IssuedAt time.Time `json:"issued_at"` + ExpiresAt string `json:"expires_at"` + IssuedAt string `json:"issued_at"` } `json:"token"` } @@ -150,8 +150,8 @@ func (kClient *KeystoneClient) AuthenticateV3() error { return err } kClient.tokenID = resp.Header.Get("X-Subject-Token") - kClient.issuedAt = response.Token.IssuedAt.String() - kClient.expiresAt = response.Token.ExpiresAt.String() + kClient.issuedAt = response.Token.IssuedAt + kClient.expiresAt = response.Token.ExpiresAt return nil } From 2f7dc9a3c4aca8c16e80fb28ac41a0bf83d71a84 Mon Sep 17 00:00:00 2001 From: Michael Henkel Date: Thu, 9 Apr 2020 12:01:56 -0700 Subject: [PATCH 6/6] fix keystone --- client.go | 6 ------ keystone.go | 8 ++++++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index d848f7d..3bc3d60 100644 --- a/client.go +++ b/client.go @@ -129,12 +129,6 @@ type TlsConfig struct { cert string } -type TlsConfig struct { - ca string - key string - cert string -} - // ListResult is the return type of the {List, ListByParent} API calls. type ListResult struct { Fq_name []string diff --git a/keystone.go b/keystone.go index 69afbde..ab77e0c 100644 --- a/keystone.go +++ b/keystone.go @@ -24,8 +24,12 @@ type KeystoneClient struct { osUsername string osPassword string osAdminToken string - current *KeystoneToken - httpClient *http.Client + current *KeystoneToken + httpClient *http.Client + tokenID string + isv3Client bool + issuedAt string + expiresAt string } // KeepaliveKeystoneClient embeds KeystoneClient