pkg/apisix/ssl.go (270 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apisix
import (
"context"
"encoding/json"
"go.uber.org/zap"
"github.com/apache/apisix-ingress-controller/pkg/apisix/cache"
"github.com/apache/apisix-ingress-controller/pkg/id"
"github.com/apache/apisix-ingress-controller/pkg/log"
v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
type sslClient struct {
url string
cluster *cluster
}
func newSSLClient(c *cluster) SSL {
if c.adminVersion == "v3" {
return &sslClient{
url: c.baseURL + "/ssls",
cluster: c,
}
}
return &sslClient{
url: c.baseURL + "/ssl",
cluster: c,
}
}
// name is namespace_sslname
func (s *sslClient) Get(ctx context.Context, name string) (*v1.Ssl, error) {
log.Debugw("try to look up ssl",
zap.String("name", name),
zap.String("url", s.url),
zap.String("cluster", s.cluster.name),
)
sid := id.GenID(name)
ssl, err := s.cluster.cache.GetSSL(sid)
if err == nil {
return ssl, nil
}
if err != cache.ErrNotFound {
log.Errorw("failed to find ssl in cache, will try to lookup from APISIX",
zap.String("name", name),
zap.Error(err),
)
} else {
log.Debugw("failed to find ssl in cache, will try to lookup from APISIX",
zap.String("name", name),
zap.Error(err),
)
}
// TODO Add mutex here to avoid dog-pile effect.
ssl, err = s.cluster.GetSSL(ctx, s.url, sid)
if err != nil {
return nil, err
}
if err := s.cluster.cache.InsertSSL(ssl); err != nil {
log.Errorf("failed to reflect ssl create to cache: %s", err)
return nil, err
}
return ssl, nil
}
// List is only used in cache warming up. So here just pass through
// to APISIX.
func (s *sslClient) List(ctx context.Context) ([]*v1.Ssl, error) {
log.Debugw("try to list ssl in APISIX",
zap.String("url", s.url),
zap.String("cluster", s.cluster.name),
)
sslItems, err := s.cluster.listResource(ctx, s.url, "ssl")
if err != nil {
log.Errorf("failed to list ssl: %s", err)
return nil, err
}
var items []*v1.Ssl
for i, item := range sslItems {
ssl, err := item.ssl()
if err != nil {
log.Errorw("failed to convert ssl item",
zap.String("url", s.url),
zap.String("ssl_key", item.Key),
zap.Error(err),
)
return nil, err
}
items = append(items, ssl)
log.Infof("list ssl #%d, body: %s", i, string(item.Value))
}
return items, nil
}
func (s *sslClient) Create(ctx context.Context, obj *v1.Ssl, shouldCompare bool) (*v1.Ssl, error) {
if v, skip := skipRequest(s.cluster, shouldCompare, s.url, obj.ID, obj); skip {
return v, nil
}
log.Debugw("try to create ssl",
zap.String("cluster", s.cluster.name),
zap.String("url", s.url),
zap.String("id", obj.ID),
)
if err := s.cluster.HasSynced(ctx); err != nil {
return nil, err
}
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
url := s.url + "/" + obj.ID
log.Debugw("creating ssl", zap.ByteString("body", data), zap.String("url", url))
resp, err := s.cluster.createResource(ctx, url, "ssl", data)
if err != nil {
log.Errorf("failed to create ssl: %s", err)
return nil, err
}
ssl, err := resp.ssl()
if err != nil {
return nil, err
}
if err := s.cluster.cache.InsertSSL(ssl); err != nil {
log.Errorf("failed to reflect ssl create to cache: %s", err)
return nil, err
}
if err := s.cluster.generatedObjCache.InsertSSL(obj); err != nil {
log.Errorf("failed to reflect generated ssl create to cache: %s", err)
return nil, err
}
return ssl, nil
}
func (s *sslClient) Delete(ctx context.Context, obj *v1.Ssl) error {
log.Debugw("try to delete ssl",
zap.String("id", obj.ID),
zap.String("cluster", s.cluster.name),
zap.String("url", s.url),
)
if err := s.cluster.HasSynced(ctx); err != nil {
return err
}
url := s.url + "/" + obj.ID
if err := s.cluster.deleteResource(ctx, url, "ssl"); err != nil {
return err
}
if err := s.cluster.cache.DeleteSSL(obj); err != nil {
log.Errorf("failed to reflect ssl delete to cache: %s", err)
if err != cache.ErrNotFound {
return err
}
}
if err := s.cluster.generatedObjCache.DeleteSSL(obj); err != nil {
log.Errorf("failed to reflect ssl delete to generated cache: %s", err)
if err != cache.ErrNotFound {
return err
}
}
return nil
}
func (s *sslClient) Update(ctx context.Context, obj *v1.Ssl, shouldCompare bool) (*v1.Ssl, error) {
if v, skip := skipRequest(s.cluster, shouldCompare, s.url, obj.ID, obj); skip {
return v, nil
}
log.Debugw("try to update ssl",
zap.String("id", obj.ID),
zap.String("cluster", s.cluster.name),
zap.String("url", s.url),
)
if err := s.cluster.HasSynced(ctx); err != nil {
return nil, err
}
url := s.url + "/" + obj.ID
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
resp, err := s.cluster.updateResource(ctx, url, "ssl", data)
if err != nil {
return nil, err
}
ssl, err := resp.ssl()
if err != nil {
return nil, err
}
if err := s.cluster.cache.InsertSSL(ssl); err != nil {
log.Errorf("failed to reflect ssl update to cache: %s", err)
return nil, err
}
if err := s.cluster.generatedObjCache.InsertSSL(obj); err != nil {
log.Errorf("failed to reflect generated ssl update to cache: %s", err)
return nil, err
}
return ssl, nil
}
type sslMem struct {
url string
resource string
cluster *cluster
keyEncryptSalt string
}
func newSSLMem(c *cluster) SSL {
return &sslMem{
url: c.baseURL + "/ssls",
resource: "ssls",
cluster: c,
keyEncryptSalt: c.sslKeyEncryptSalt,
}
}
func (r *sslMem) Get(ctx context.Context, name string) (*v1.Ssl, error) {
log.Debugw("try to look up ssl",
zap.String("name", name),
zap.String("cluster", r.cluster.name),
)
rid := id.GenID(name)
ssl, err := r.cluster.cache.GetSSL(rid)
if err != nil {
log.Errorw("failed to find ssl in cache, will try to lookup from APISIX",
zap.String("name", name),
zap.Error(err),
)
return nil, err
}
return ssl, nil
}
// List is only used in cache warming up. So here just pass through
// to APISIX.
func (r *sslMem) List(ctx context.Context) ([]*v1.Ssl, error) {
log.Debugw("try to list resource in APISIX",
zap.String("cluster", r.cluster.name),
zap.String("resource", r.resource),
)
ssls, err := r.cluster.cache.ListSSL()
if err != nil {
log.Errorf("failed to list %s: %s", r.resource, err)
return nil, err
}
return ssls, nil
}
func (r *sslMem) Create(ctx context.Context, obj *v1.Ssl, shouldCompare bool) (*v1.Ssl, error) {
if ssl, _ := r.cluster.cache.GetSSL(obj.ID); ssl != nil {
return r.Update(ctx, obj, shouldCompare)
}
pkey, err := AesEencryptPrivatekey([]byte(obj.Key), []byte(r.keyEncryptSalt))
if err != nil {
return nil, err
}
obj.Key = pkey
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
r.cluster.CreateResource(r.resource, obj.ID, data)
if err := r.cluster.cache.InsertSSL(obj); err != nil {
log.Errorf("failed to reflect ssl create to cache: %s", err)
return nil, err
}
return obj, nil
}
func (r *sslMem) Delete(ctx context.Context, obj *v1.Ssl) error {
data, err := json.Marshal(obj)
if err != nil {
return err
}
r.cluster.DeleteResource(r.resource, obj.ID, data)
return nil
}
func (r *sslMem) Update(ctx context.Context, obj *v1.Ssl, shouldCompare bool) (*v1.Ssl, error) {
pkey, err := AesEencryptPrivatekey([]byte(obj.Key), []byte(r.keyEncryptSalt))
if err != nil {
return nil, err
}
obj.Key = pkey
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
r.cluster.UpdateResource(r.resource, obj.ID, data)
if err := r.cluster.cache.InsertSSL(obj); err != nil {
log.Errorf("failed to reflect ssl update to cache: %s", err)
return nil, err
}
return obj, nil
}