diff --git a/README.md b/README.md index b8347e9..cefc4c7 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,16 @@ cat resources/envoy.yaml |sed 's/$IP/'$(docker inspect -f '{{range .NetworkSetti Then run the envoy proxy: ``` -docker run --rm -it -p 10000:10000 -p 10001:10001 -p 9901:9901 --network roxprox -v "$(PWD)/resources/envoy-withip.yaml":/etc/envoy/envoy.yaml envoyproxy/envoy:v1.14.1 +docker run --rm -it -p 10000:10000 -p 10001:10001 -p 9901:9901 --network roxprox -v "$(PWD)/resources/envoy-withip.yaml":/etc/envoy/envoy.yaml envoyproxy/envoy:v1.15-latest ``` + +## Run access log serve +``` +cd resources/access-log-server +make docker +docker run --rm -it -p 9001:9001 --network roxprox --name als als +``` + ## Configuration You can configure endpoints using yaml definitions. Below are example yaml definitions that you can put in your data/ folder. @@ -84,6 +92,17 @@ spec: port: 443 ``` +### ALS +``` +api: proxy.in4it.io/v1 +kind: accessLogServer +metadata: + name: accessLogServerExample +spec: + address: "als" + port: 9001 +``` + ### Authn ``` api: proxy.in4it.io/v1 diff --git a/go.mod b/go.mod index 656cf11..d23ec58 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.12 require ( github.com/aws/aws-sdk-go v1.32.10 - github.com/envoyproxy/go-control-plane v0.9.6-0.20200618221453-226baa5cddab + github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354 // indirect + github.com/envoyproxy/go-control-plane v0.9.6 github.com/ghodss/yaml v1.0.0 github.com/gogo/protobuf v1.3.1 github.com/golang/protobuf v1.4.2 diff --git a/go.sum b/go.sum index c132758..d388314 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrC github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533 h1:8wZizuKuZVu5COB7EsBYxBQz8nRcXXn5d4Gt91eJLvU= github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354 h1:9kRtNpqLHbZVO/NNxhHp2ymxFxsHOe3x2efJGn//Tas= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/envoy v1.14.1 h1:iqLWWa0bsImtur3PIm59jEczqu8/7q8JbNx1eFidLJA= @@ -20,6 +22,7 @@ github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1r github.com/envoyproxy/go-control-plane v0.8.4 h1:moNlmfa71yZkzDxAb4Fz5qwaW1giZmTtwn6P/gYIK6E= github.com/envoyproxy/go-control-plane v0.8.4/go.mod h1:XB9+ce7x+IrsjgIVnRnql0O61gj/np0/bGDfhJI3sCU= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.2 h1:GJ5MKABRjz+QuET1GHm0KD9HC/mAzb3g2FznLQ0aThc= github.com/envoyproxy/go-control-plane v0.9.2/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -27,6 +30,8 @@ github.com/envoyproxy/go-control-plane v0.9.5 h1:lRJIqDD8yjV1YyPRqecMdytjDLs2fTX github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= github.com/envoyproxy/go-control-plane v0.9.6-0.20200618221453-226baa5cddab h1:ALuQRm7L5dY+9HEFlo5il+sENgolnfjWRHKSf8TgYf4= github.com/envoyproxy/go-control-plane v0.9.6-0.20200618221453-226baa5cddab/go.mod h1:JvuSsUgXzeWfLVfAe9OeW40eBtd+E8yMydqNm0iuBxs= +github.com/envoyproxy/go-control-plane v0.9.6 h1:GgblEiDzxf5ajlAZY4aC8xp7DwkrGfauFNMGdB2bBv0= +github.com/envoyproxy/go-control-plane v0.9.6/go.mod h1:GFqM7v0B62MraO4PWRedIbhThr/Rf7ev6aHOOPXeaDA= github.com/envoyproxy/protoc-gen-validate v0.0.0-20190405222122-d6164de49109 h1:FNgqGzbOm637YKRbYGKb9cqGo8i50++w/LWvMau7jrw= github.com/envoyproxy/protoc-gen-validate v0.0.0-20190405222122-d6164de49109/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.0.14 h1:YBW6/cKy9prEGRYLnaGa4IDhzxZhRCtKsax8srGKDnM= @@ -147,6 +152,7 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/pkg/api/accesslogserver.go b/pkg/api/accesslogserver.go new file mode 100644 index 0000000..98af325 --- /dev/null +++ b/pkg/api/accesslogserver.go @@ -0,0 +1,14 @@ +package api + +type AccessLogServer struct { + API string `json:"api" yaml:"api"` + Kind string `json:"kind" yaml:"kind"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec AccessLogServerSpec `json:"spec" yaml:"spec"` +} +type AccessLogServerSpec struct { + Address string `json:"address" yaml:"address"` + Port int64 `json:"port" yaml:"port"` + AdditionalRequestHeadersToLog []string `json:"additionalRequestHeadersToLog" yaml:"additionalRequestHeadersToLog"` + AdditionalResponseHeadersToLog []string `json:"additionalResponseHeadersToLog" yaml:"additionalResponseHeadersToLog"` +} diff --git a/pkg/envoy/accesslogserver.go b/pkg/envoy/accesslogserver.go new file mode 100644 index 0000000..388ed56 --- /dev/null +++ b/pkg/envoy/accesslogserver.go @@ -0,0 +1,82 @@ +package envoy + +import ( + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + + alf "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" + api "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + als "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" + "github.com/golang/protobuf/ptypes" +) + +type AccessLogServer struct{} + +func newAccessLogServer() *AccessLogServer { + return &AccessLogServer{} +} + +func (c *AccessLogServer) updateListenersWithAccessLogServer(cache *WorkQueueCache, params AccessLogServerParams) error { + // update listener + for listenerKey := range cache.listeners { + ll := cache.listeners[listenerKey].(*api.Listener) + for filterchainID := range ll.FilterChains { + for filterID := range ll.FilterChains[filterchainID].Filters { + // get manager + manager, err := getManager((ll.FilterChains[filterchainID].Filters[filterID].ConfigType).(*api.Filter_TypedConfig)) + if err != nil { + return err + } + accessLogConfig, err := c.getAccessLoggerConfig(params) + if err != nil { + return err + } + + manager.AccessLog = accessLogConfig + + // update manager in cache + pbst, err := ptypes.MarshalAny(&manager) + if err != nil { + return err + } + ll.FilterChains[filterchainID].Filters[filterID].ConfigType = &api.Filter_TypedConfig{ + TypedConfig: pbst, + } + } + } + } + + return nil +} + +func (c *AccessLogServer) getAccessLoggerConfig(params AccessLogServerParams) ([]*alf.AccessLog, error) { + if params.Name != "" { + alsConfig := &als.HttpGrpcAccessLogConfig{ + CommonConfig: &als.CommonGrpcAccessLogConfig{ + TransportApiVersion: core.ApiVersion_V3, + LogName: params.Name, + GrpcService: &core.GrpcService{ + TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ + ClusterName: params.Name, + }, + }, + }, + }, + AdditionalRequestHeadersToLog: params.AdditionalRequestHeadersToLog, + AdditionalResponseHeadersToLog: params.AdditionalResponseHeadersToLog, + } + alsConfigPbst, err := ptypes.MarshalAny(alsConfig) + if err != nil { + return nil, err + } + + return []*alf.AccessLog{{ + Name: wellknown.HTTPGRPCAccessLog, + ConfigType: &alf.AccessLog_TypedConfig{ + TypedConfig: alsConfigPbst, + }, + }}, nil + } + return nil, nil +} diff --git a/pkg/envoy/listener.go b/pkg/envoy/listener.go index f09d2d9..55e2c23 100644 --- a/pkg/envoy/listener.go +++ b/pkg/envoy/listener.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + alf "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" api "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -24,8 +25,9 @@ const Error_NoFilterChainFound = "NoFilterChainFound" const Error_NoFilterFound = "NoFilterFound" type Listener struct { - httpFilter []*hcm.HttpFilter - tracing *hcm.HttpConnectionManager_Tracing + httpFilter []*hcm.HttpFilter + tracing *hcm.HttpConnectionManager_Tracing + accessLoggerConfig []*alf.AccessLog } func newListener() *Listener { @@ -35,6 +37,8 @@ func newListener() *Listener { Name: "envoy.filters.http.router", }, } + listener.accessLoggerConfig = []*alf.AccessLog{} + return listener } @@ -467,6 +471,7 @@ func (l *Listener) newManager(routeName string, virtualHosts []*route.VirtualHos }, }, HttpFilters: httpFilters, + AccessLog: l.accessLoggerConfig, } if l.tracing != nil { httpConnectionManager.Tracing = l.tracing @@ -715,6 +720,21 @@ func (l *Listener) updateDefaultCompressionSetting(compressionParams Compression updateHTTPFilterWithConfig(&l.httpFilter, "envoy.filters.http.compressor", compressorFilterEncoded) } +func (l *Listener) updateDefaultAccessLogServer(accessLogServerParams AccessLogServerParams) { + c := newAccessLogServer() + accessLoggerConfig, err := c.getAccessLoggerConfig(accessLogServerParams) + if err != nil { + logger.Errorf("Couldn't get access logger config: %s", err) + return + } + if accessLoggerConfig == nil { + return + } + + l.accessLoggerConfig = accessLoggerConfig + +} + func (l *Listener) newHTTPRouterFilter() []*hcm.HttpFilter { return l.httpFilter } diff --git a/pkg/envoy/testdata/test-accesslogserver.yaml b/pkg/envoy/testdata/test-accesslogserver.yaml new file mode 100644 index 0000000..b34a57f --- /dev/null +++ b/pkg/envoy/testdata/test-accesslogserver.yaml @@ -0,0 +1,7 @@ +api: proxy.in4it.io/v1 +kind: accessLogServer +metadata: + name: accessLogServerExample +spec: + address: "localhost" + port: 9001 diff --git a/pkg/envoy/types.go b/pkg/envoy/types.go index 0a17878..20018c0 100644 --- a/pkg/envoy/types.go +++ b/pkg/envoy/types.go @@ -6,18 +6,19 @@ import ( ) type WorkQueueItem struct { - id string - Action string - DependsOn string - DependsOnItemIDs []string - TLSParams TLSParams - ClusterParams ClusterParams - ListenerParams ListenerParams - ChallengeParams ChallengeParams - CreateCertParams CreateCertParams - TracingParams TracingParams - CompressionParams CompressionParams - state string + id string + Action string + DependsOn string + DependsOnItemIDs []string + TLSParams TLSParams + ClusterParams ClusterParams + ListenerParams ListenerParams + ChallengeParams ChallengeParams + CreateCertParams CreateCertParams + TracingParams TracingParams + CompressionParams CompressionParams + AccessLogServerParams AccessLogServerParams + state string } type WorkQueueCache struct { @@ -132,6 +133,12 @@ type CompressionParams struct { DisableOnEtagHeader bool } +type AccessLogServerParams struct { + Name string + AdditionalRequestHeadersToLog []string + AdditionalResponseHeadersToLog []string +} + type DirectResponse struct { Status uint32 Body string diff --git a/pkg/envoy/workqueue.go b/pkg/envoy/workqueue.go index cf3b569..220412c 100644 --- a/pkg/envoy/workqueue.go +++ b/pkg/envoy/workqueue.go @@ -13,19 +13,20 @@ import ( ) type WorkQueue struct { - cs chan WorkQueueSubmissionState - c chan WorkQueueItem - callback *Callback - cache WorkQueueCache - cert *Cert - listener *Listener - jwtProvider *JwtProvider - authzFilter *AuthzFilter - tracing *Tracing - compression *Compression - cluster *Cluster - acmeContact string - latestSnapshot cache.Snapshot + cs chan WorkQueueSubmissionState + c chan WorkQueueItem + callback *Callback + cache WorkQueueCache + cert *Cert + listener *Listener + jwtProvider *JwtProvider + authzFilter *AuthzFilter + tracing *Tracing + compression *Compression + accessLogServer *AccessLogServer + cluster *Cluster + acmeContact string + latestSnapshot cache.Snapshot } func NewWorkQueue(s storage.Storage, acmeContact string) (*WorkQueue, error) { @@ -42,15 +43,16 @@ func NewWorkQueue(s storage.Storage, acmeContact string) (*WorkQueue, error) { } w := &WorkQueue{ - c: c, - cs: cs, - cert: cert, - listener: newListener(), - cluster: newCluster(), - jwtProvider: newJwtProvider(), - authzFilter: newAuthzFilter(), - tracing: newTracing(), - compression: newCompression(), + c: c, + cs: cs, + cert: cert, + listener: newListener(), + cluster: newCluster(), + jwtProvider: newJwtProvider(), + authzFilter: newAuthzFilter(), + tracing: newTracing(), + compression: newCompression(), + accessLogServer: newAccessLogServer(), } // run queue to resolve dependencies @@ -211,6 +213,18 @@ func (w *WorkQueue) Submit(items []WorkQueueItem) (string, error) { item.state = "finished" } updateXds = true + case "updateListenersWithAccessLogServer": + // update default listener route + w.listener.updateDefaultAccessLogServer(item.AccessLogServerParams) + // update existing listeners + err := w.accessLogServer.updateListenersWithAccessLogServer(&w.cache, item.AccessLogServerParams) + if err != nil { + item.state = "error" + logger.Errorf("updateListenersWithAccessLogServer error: %s", err) + } else { + item.state = "finished" + } + updateXds = true case "updateListenerWithChallenge": err := w.listener.updateListenerWithChallenge(&w.cache, item.ChallengeParams) if err != nil { diff --git a/pkg/envoy/xds.go b/pkg/envoy/xds.go index 274dff7..0c53f9b 100644 --- a/pkg/envoy/xds.go +++ b/pkg/envoy/xds.go @@ -226,6 +226,13 @@ func (x *XDS) ImportObject(object pkgApi.Object) ([]WorkQueueItem, error) { return []WorkQueueItem{}, fmt.Errorf("Couldn't import new rule: %s", err) } return items, nil + case "accessLogServer": + accessLogServer := object.Data.(pkgApi.AccessLogServer) + items, err := x.importAccessLogServer(accessLogServer) + if err != nil { + return []WorkQueueItem{}, fmt.Errorf("Couldn't import new rule: %s", err) + } + return items, nil } return []WorkQueueItem{}, nil @@ -283,6 +290,19 @@ func (x *XDS) importCompression(compression pkgApi.Compression) ([]WorkQueueItem }, nil } +func (x *XDS) importAccessLogServer(accessLogServer pkgApi.AccessLogServer) ([]WorkQueueItem, error) { + return []WorkQueueItem{ + { + Action: "updateListenersWithAccessLogServer", + AccessLogServerParams: AccessLogServerParams{ + Name: accessLogServer.Metadata.Name, + AdditionalRequestHeadersToLog: accessLogServer.Spec.AdditionalRequestHeadersToLog, + AdditionalResponseHeadersToLog: accessLogServer.Spec.AdditionalResponseHeadersToLog, + }, + }, + }, nil +} + func (x *XDS) importJwtProvider(jwtProvider pkgApi.JwtProvider) ([]WorkQueueItem, error) { logger.Debugf("Found jwtProvider with name %s and jwksUrl %s", jwtProvider.Metadata.Name, jwtProvider.Spec.RemoteJwks) u, err := url.Parse(jwtProvider.Spec.RemoteJwks) diff --git a/pkg/envoy/xds_test.go b/pkg/envoy/xds_test.go index 1e24ea3..3c26311 100644 --- a/pkg/envoy/xds_test.go +++ b/pkg/envoy/xds_test.go @@ -8,6 +8,9 @@ import ( listenerAPI "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + als "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" + "github.com/golang/protobuf/ptypes" "github.com/in4it/roxprox/pkg/storage" localStorage "github.com/in4it/roxprox/pkg/storage/local" "github.com/juju/loggo" @@ -623,3 +626,58 @@ func TestCompressionObject(t *testing.T) { return } } +func TestAccessLogServer(t *testing.T) { + logger.SetLogLevel(loggo.DEBUG) + s, err := initStorage() + if err != nil { + t.Errorf("Couldn't initialize storage: %s", err) + return + } + x := NewXDS(s, "", "") + ObjectFileNames := []string{"test1.yaml", "test-accesslogserver.yaml"} + for _, filename := range ObjectFileNames { + newItems, err := x.putObject(filename) + if err != nil { + t.Errorf("PutObject failed: %s", err) + return + } + _, err = x.workQueue.Submit(newItems) + if err != nil { + t.Errorf("WorkQueue error: %s", err) + return + } + } + + if len(x.workQueue.cache.listeners) == 0 { + t.Errorf("No Listeners") + return + } + + for _, listener := range x.workQueue.cache.listeners { + ll := listener.(*listenerAPI.Listener) + manager, err := getListenerHTTPConnectionManager(ll) + if err != nil { + t.Errorf("Error while getting listener: %s", err) + return + } + if len(manager.AccessLog) == 0 { + t.Errorf("No Access Log Configuration found") + return + } + if manager.AccessLog[0].Name != wellknown.HTTPGRPCAccessLog { + t.Errorf("Access log has wrong name") + return + } + var alsConfig als.HttpGrpcAccessLogConfig + err = ptypes.UnmarshalAny(manager.AccessLog[0].GetTypedConfig(), &alsConfig) + if err != nil { + t.Errorf("Cannot unmarshal HttpGrpcAccessLogConfig typed config") + return + } + if alsConfig.CommonConfig.LogName != "als_accessLogServerExample" { + t.Errorf("LogName is not correct within alsConfig: %s", alsConfig.CommonConfig.LogName) + return + } + + } +} diff --git a/pkg/storage/local/io.go b/pkg/storage/local/io.go index 30279cc..118308c 100644 --- a/pkg/storage/local/io.go +++ b/pkg/storage/local/io.go @@ -147,6 +147,13 @@ func (l *LocalStorage) GetObject(name string) ([]api.Object, error) { return objects, err } object.Data = compression + case "accessLogServer": + var accessLogServer api.AccessLogServer + err = yaml.Unmarshal([]byte(contentsSplitted), &accessLogServer) + if err != nil { + return objects, err + } + object.Data = accessLogServer default: return objects, errors.New("Rule in wrong format") } diff --git a/pkg/storage/s3/io.go b/pkg/storage/s3/io.go index 3108fb7..dcf444b 100644 --- a/pkg/storage/s3/io.go +++ b/pkg/storage/s3/io.go @@ -176,6 +176,13 @@ func (s *S3Storage) GetObject(filename string) ([]api.Object, error) { return objects, err } object.Data = compression + case "accessLogServer": + var accessLogServer api.AccessLogServer + err = yaml.Unmarshal([]byte(contentsSplitted), &accessLogServer) + if err != nil { + return objects, err + } + object.Data = accessLogServer default: return objects, errors.New("Object in wrong format") } diff --git a/resources/access-log-server/Dockerfile b/resources/access-log-server/Dockerfile new file mode 100644 index 0000000..a79c77b --- /dev/null +++ b/resources/access-log-server/Dockerfile @@ -0,0 +1,4 @@ +FROM alpine:latest +ADD build/als /usr/local/bin/als +EXPOSE 9001 +ENTRYPOINT ["/usr/local/bin/als"] diff --git a/resources/access-log-server/Makefile b/resources/access-log-server/Makefile new file mode 100644 index 0000000..1d43c36 --- /dev/null +++ b/resources/access-log-server/Makefile @@ -0,0 +1,11 @@ +build: + CGO_ENABLED=0 GOOS=linux go build \ + -a --ldflags '-extldflags "-static"' -tags netgo -installsuffix netgo \ + -o build/als server.go + +docker: build + docker build -t als . + +.PHONY: clean +clean: + rm -fr build diff --git a/resources/access-log-server/server.go b/resources/access-log-server/server.go new file mode 100644 index 0000000..3b65242 --- /dev/null +++ b/resources/access-log-server/server.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "log" + "net" + "time" + + alf "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3" + accessloggrpc "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" + v3 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" + "google.golang.org/grpc" +) + +func main() { + + alsv3 := &AccessLogService{} + + grpcServer := grpc.NewServer() + v3.RegisterAccessLogServiceServer(grpcServer, alsv3) + l, err := net.Listen("tcp", ":9001") + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + log.Println("Listening on tcp://localhost:9001") + grpcServer.Serve(l) +} + +type AccessLogService struct{} + +func (s *AccessLogService) StreamAccessLogs(stream accessloggrpc.AccessLogService_StreamAccessLogsServer) error { + log.Println("Started stream") + var logName string + for { + msg, err := stream.Recv() + if err != nil { + return err + } + if msg.Identifier != nil { + logName = msg.Identifier.LogName + log.Println("Log name:", logName) + } + switch entries := msg.LogEntries.(type) { + case *accessloggrpc.StreamAccessLogsMessage_HttpLogs: + for _, entry := range entries.HttpLogs.LogEntry { + if entry != nil { + common := entry.CommonProperties + req := entry.Request + resp := entry.Response + if common == nil { + common = &alf.AccessLogCommon{} + } + if req == nil { + req = &alf.HTTPRequestProperties{} + } + if resp == nil { + resp = &alf.HTTPResponseProperties{} + } + log.Println(fmt.Sprintf("[%s-%s] %s %s %s %d %s %s", + logName, time.Now().Format(time.RFC3339), req.Authority, req.Path, req.Scheme, + resp.ResponseCode.GetValue(), req.RequestId, common.UpstreamCluster)) + } + } + case *accessloggrpc.StreamAccessLogsMessage_TcpLogs: + for _, entry := range entries.TcpLogs.LogEntry { + if entry != nil { + common := entry.CommonProperties + if common == nil { + common = &alf.AccessLogCommon{} + } + log.Println(fmt.Sprintf("[%s-%s] tcp %s %s", + logName, time.Now().Format(time.RFC3339), common.UpstreamLocalAddress, common.UpstreamCluster)) + } + } + default: + log.Println("empty log message") + } + } +} diff --git a/resources/envoy-als.yaml b/resources/envoy-als.yaml new file mode 100644 index 0000000..505fc66 --- /dev/null +++ b/resources/envoy-als.yaml @@ -0,0 +1,50 @@ +dynamic_resources: + ads_config: + api_type: GRPC + transport_api_version: V3 + grpc_services: + envoy_grpc: + cluster_name: xds_cluster + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 +node: + cluster: ingress-gateway + id: ingress-gateway-2 +static_resources: + clusters: + - name: xds_cluster + connect_timeout: 1s + http2_protocol_options: {} + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: $IP + port_value: 8080 + + - name: accessLogServerExample + connect_timeout: 5s + type: LOGICAL_DNS + http2_protocol_options: {} + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: accessLogServerExample # must to be the same name as the metadata in the `api: proxy.in4it.io/v1` for `kind: accessLogServer` + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: als + port_value: 9001 +#admin: +# access_log_path: /tmp/admin_access.log +# address: +# socket_address: { address: 0.0.0.0, port_value: 9901 } diff --git a/resources/example-proxy/mocky.yaml b/resources/example-proxy/mocky.yaml index 7e884a6..6f275ac 100644 --- a/resources/example-proxy/mocky.yaml +++ b/resources/example-proxy/mocky.yaml @@ -10,3 +10,11 @@ spec: - proxy: hostname: www.mocky.io port: 443 +--- +api: proxy.in4it.io/v1 +kind: accessLogServer +metadata: + name: accessLogServerExample +spec: + address: "als" + port: 9001 diff --git a/terraform/envoy-proxy.tf b/terraform/envoy-proxy.tf index 2c58973..2cfc882 100644 --- a/terraform/envoy-proxy.tf +++ b/terraform/envoy-proxy.tf @@ -2,21 +2,25 @@ # envoy (http) # -data "template_file" "envoy-config-http" { - template = var.enable_datadog ? file("${path.module}/templates/envoy-datadog.yml") : file("${path.module}/templates/envoy.yml") - vars = { - CLUSTER = "roxprox" - ID = "roxprox-http" - ADDRESS = "roxprox.roxprox.local" - DATADOG = "datadog.roxprox.local" - ADMIN_PORT = "9909" +locals { + envoy_config_vars = { + CLUSTER = "roxprox" + ID = "roxprox-http" + ADDRESS = "roxprox.roxprox.local" + DATADOG = "datadog.roxprox.local" + ADMIN_PORT = "9909" + ALS_CLUSTER_NAME = var.envoy_als_cluster_name + ALS_ADDRESS = var.envoy_als_address + ALS_PORT = var.envoy_als_port + ENABLE_ALS = var.enable_als + ENABLE_DATADOG = var.enable_datadog } } resource "aws_ssm_parameter" "envoy-config-http" { name = "/roxprox/envoy.yaml" type = "String" - value = base64encode(trimspace(data.template_file.envoy-config-http.rendered)) + value = base64encode(jsonencode(jsondecode(templatefile("${path.module}/templates/envoy-config.tmpl", local.envoy_config_vars)))) } @@ -99,18 +103,6 @@ resource "aws_ecs_service" "envoy-proxy" { # envoy (https) # -data "template_file" "envoy-config-https" { - count = var.tls_listener ? 1 : 0 - template = var.enable_datadog ? file("${path.module}/templates/envoy-datadog.yml") : file("${path.module}/templates/envoy.yml") - vars = { - CLUSTER = "roxprox" - ID = "roxprox-https" - ADDRESS = "roxprox.roxprox.local" - DATADOG = "datadog.roxprox.local" - ADMIN_PORT = "9909" - } -} - data "template_file" "envoy-proxy-https" { count = var.tls_listener ? 1 : 0 template = var.enable_appmesh ? file("${path.module}/templates/envoy-appmesh.json.tpl") : file("${path.module}/templates/envoy.json.tpl") @@ -118,7 +110,7 @@ data "template_file" "envoy-proxy-https" { vars = { AWS_REGION = data.aws_region.current.name ENVOY_RELEASE = var.envoy_release - ENVOY_CONFIG = aws_ssm_parameter.envoy-config-https[0].arn + ENVOY_CONFIG = aws_ssm_parameter.envoy-config-http.arn APPMESH_NAME = var.appmesh_name APPMESH_ENVOY_RELEASE = var.appmesh_envoy_release EXTRA_CONTAINERS = var.extra_containers == "" ? "" : ",${var.extra_containers}" @@ -126,13 +118,6 @@ data "template_file" "envoy-proxy-https" { } } -resource "aws_ssm_parameter" "envoy-config-https" { - count = var.tls_listener ? 1 : 0 - name = "/roxprox/envoy-https.yaml" - type = "String" - value = base64encode(trimspace(data.template_file.envoy-config-https[0].rendered)) -} - resource "aws_ecs_task_definition" "envoy-proxy-https" { count = var.tls_listener ? 1 : 0 family = "envoy-proxy-https" diff --git a/terraform/templates/envoy-config.tmpl b/terraform/templates/envoy-config.tmpl new file mode 100644 index 0000000..acdc939 --- /dev/null +++ b/terraform/templates/envoy-config.tmpl @@ -0,0 +1,118 @@ +{ + "dynamic_resources": { + "ads_config": { + "api_type": "GRPC", + "transport_api_version": "V3", + "grpc_services": { + "envoy_grpc": { + "cluster_name": "xds_cluster" + } + } + }, + "cds_config": { + "ads": {}, + "resource_api_version": "V3" + }, + "lds_config": { + "ads": {}, + "resource_api_version": "V3" + } + }, + "node": { + "cluster": "${CLUSTER}", + "id": "${ID}" + }, + "static_resources": { + "clusters": [ + %{ if ENABLE_ALS } + { + "name": "${ALS_CLUSTER_NAME}", + "type": "LOGICAL_DNS", + "connect_timeout": "5s", + "http2_protocol_options": {}, + "dns_lookup_family": "V4_ONLY", + "load_assignment": { + "cluster_name": "${ALS_CLUSTER_NAME}", + "endpoints": [ + { + "lb_endpoints": [ + { + "endpoint": { + "address": { + "socket_address": { + "address": "${ALS_ADDRESS}", + "port_value": "${ALS_PORT}" + } + } + } + } + ] + } + ] + } + }, + %{ endif } + %{ if ENABLE_DATADOG } + { + "name": "datadog_agent", + "connect_timeout": "1s", + "type": "strict_dns", + "lb_policy": "round_robin", + "hosts": [ + { + "socket_address": { + "address": "${DATADOG}", + "port_value": 8126 + } + } + ] + }, + %{ endif } + { + "name": "xds_cluster", + "connect_timeout": "1s", + "type": "strict_dns", + "http2_protocol_options": {}, + "load_assignment": { + "cluster_name": "xds_cluster", + "endpoints": [ + { + "lb_endpoints": [ + { + "endpoint": { + "address": { + "socket_address": { + "address": "${ADDRESS}", + "port_value": 8080 + } + } + } + } + ] + } + ] + } + } + ] + }, + %{ if ENABLE_DATADOG } + "tracing": { + "http": { + "name": "envoy.tracers.datadog", + "config": { + "collector_cluster": "datadog_agent", + "service_name": "envoy" + } + } + }, + %{ endif } + "admin": { + "access_log_path": "/tmp/admin_access.log", + "address": { + "socket_address": { + "address": "0.0.0.0", + "port_value": ${ADMIN_PORT} + } + } + } +} \ No newline at end of file diff --git a/terraform/templates/envoy-datadog.yml b/terraform/templates/envoy-datadog.yml deleted file mode 100644 index 84767ca..0000000 --- a/terraform/templates/envoy-datadog.yml +++ /dev/null @@ -1 +0,0 @@ -{ "dynamic_resources": { "ads_config": { "api_type": "GRPC", "transport_api_version": "V3", "grpc_services": { "envoy_grpc": { "cluster_name": "xds_cluster" } } }, "cds_config": { "ads": { }, "resource_api_version": "V3" }, "lds_config": { "ads": { }, "resource_api_version": "V3" } }, "node": { "cluster": "${CLUSTER}", "id": "${ID}" }, "static_resources": { "clusters": [ { "name": "xds_cluster", "connect_timeout": "1s", "type": "strict_dns", "http2_protocol_options": { }, "load_assignment": { "cluster_name": "xds_cluster", "endpoints": [ { "lb_endpoints": [ { "endpoint": { "address": { "socket_address": { "address": "${ADDRESS}", "port_value": 8080 } } } } ] } ] } }, { "name": "datadog_agent", "connect_timeout": "1s", "type": "strict_dns", "lb_policy": "round_robin", "hosts": [ { "socket_address": { "address": "${DATADOG}", "port_value": 8126 } } ] } ] }, "tracing": { "http": { "name": "envoy.tracers.datadog", "config": { "collector_cluster": "datadog_agent", "service_name": "envoy" } } }, "admin": { "access_log_path": "/tmp/admin_access.log", "address": { "socket_address": { "address": "0.0.0.0", "port_value": ${ADMIN_PORT} } } } } diff --git a/terraform/templates/envoy.yml b/terraform/templates/envoy.yml deleted file mode 100644 index a53608b..0000000 --- a/terraform/templates/envoy.yml +++ /dev/null @@ -1 +0,0 @@ -{ "dynamic_resources": { "ads_config": { "api_type": "GRPC", "transport_api_version": "V3", "grpc_services": { "envoy_grpc": { "cluster_name": "xds_cluster" } } }, "cds_config": { "ads": { }, "resource_api_version": "V3" }, "lds_config": { "ads": { }, "resource_api_version": "V3" } }, "node": { "cluster": "${CLUSTER}", "id": "${ID}" }, "static_resources": { "clusters": [ { "name": "xds_cluster", "connect_timeout": "1s", "type": "strict_dns", "http2_protocol_options": { }, "load_assignment": { "cluster_name": "xds_cluster", "endpoints": [ { "lb_endpoints": [ { "endpoint": { "address": { "socket_address": { "address": "${ADDRESS}", "port_value": 8080 } } } } ] } ] } } ] }, "admin": { "access_log_path": "/tmp/admin_access.log", "address": { "socket_address": { "address": "0.0.0.0", "port_value": ${ADMIN_PORT} } } } } diff --git a/terraform/variables.tf b/terraform/variables.tf index bf29327..be413dd 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -1,6 +1,6 @@ variable "envoy_release" { description = "docker tag of envoy release" - default = "v1.14.1" + default = "v1.15.0" } variable "release" { @@ -40,6 +40,26 @@ variable "envoy_proxy_appmesh_memory" { default = 1024 } +variable "enable_als" { + description = "flag to enable ALS integration" + default = false +} + +variable "envoy_als_cluster_name" { + description = "envoy access log server cluster name" + default = "als_cluster" +} + +variable "envoy_als_address" { + description = "envoy access log server address" + default = "als" +} + +variable "envoy_als_port" { + description = "envoiy access log server port" + default = 9001 +} + variable "subnets" { type = list(string)