|
| 1 | +/* |
| 2 | +Copyright 2023 Red Hat, Inc. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package service |
| 18 | + |
| 19 | +import ( |
| 20 | + "net/url" |
| 21 | + "reflect" |
| 22 | + "strings" |
| 23 | + |
| 24 | + envoycore "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" |
| 25 | + envoyauth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" |
| 26 | + "github.com/golang/protobuf/ptypes/timestamp" |
| 27 | +) |
| 28 | + |
| 29 | +type WellKnownAttributes struct { |
| 30 | + // Dynamic request metadata |
| 31 | + Metadata *envoycore.Metadata `json:"metadata,omitempty"` |
| 32 | + // Request attributes |
| 33 | + Request *RequestAttributes `json:"request,omitempty"` |
| 34 | + // Source attributes |
| 35 | + Source *SourceAttributes `json:"source,omitempty"` |
| 36 | + // Destination attributes |
| 37 | + Destination *DestinationAttributes `json:"destination,omitempty"` |
| 38 | + // Auth attributes |
| 39 | + Auth *AuthAttributes `json:"auth,omitempty"` |
| 40 | +} |
| 41 | + |
| 42 | +type RequestAttributes struct { |
| 43 | + // Request ID corresponding to x-request-id header value |
| 44 | + Id string `json:"id,omitempty"` |
| 45 | + // Time of the first byte received |
| 46 | + Time *timestamp.Timestamp `json:"time,omitempty"` |
| 47 | + // Request protocol (“HTTP/1.0”, “HTTP/1.1”, “HTTP/2”, or “HTTP/3”) |
| 48 | + Protocol string `json:"protocol,omitempty"` |
| 49 | + // The scheme portion of the URL e.g. “http” |
| 50 | + Scheme string `json:"scheme,omitempty"` |
| 51 | + // The host portion of the URL e.g. “example.com” |
| 52 | + Host string `json:"host,omitempty"` |
| 53 | + // Request method e.g. “GET” |
| 54 | + Method string `json:"method,omitempty"` |
| 55 | + // The path portion of the URL e.g. “/foo?bar=baz” |
| 56 | + Path string `json:"path,omitempty"` |
| 57 | + // The path portion of the URL without the query string e.g. “/foo” |
| 58 | + URLPath string `json:"url_path,omitempty"` |
| 59 | + // The query portion of the URL in the format of “name1=value1&name2=value2” |
| 60 | + Query string `json:"query,omitempty"` |
| 61 | + // All request headers indexed by the lower-cased header name e.g. “accept-encoding”: “gzip” |
| 62 | + Headers map[string]string `json:"headers,omitempty"` |
| 63 | + // Referer request header e.g. “https://www.kuadrant.io/” |
| 64 | + Referer string `json:"referer,omitempty"` |
| 65 | + // User agent request header e.g. “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/…” |
| 66 | + UserAgent string `json:"user_agent,omitempty"` |
| 67 | + // The HTTP request size in bytes. If unknown, it must be -1 e.g. 1234 |
| 68 | + Size int64 `json:"size,omitempty"` |
| 69 | + // The HTTP request body. (Disabled by default. Requires additional proxy configuration to enabled it.) e.g. “…” |
| 70 | + Body string `json:"body,omitempty"` |
| 71 | + // The HTTP request body in bytes. This is sometimes used instead of body depending on the proxy configuration. e.g. 1234 |
| 72 | + RawBody []byte `json:"raw_body,omitempty"` |
| 73 | + // This is analogous to request.headers, however these contents are not sent to the upstream server. It provides an |
| 74 | + // extension mechanism for sending additional information to the auth service without modifying the proto definition. |
| 75 | + // It maps to the internal opaque context in the proxy filter chain. (Requires additional configuration in the proxy.) |
| 76 | + ContextExtensions map[string]string `json:"context_extensions,omitempty"` |
| 77 | +} |
| 78 | + |
| 79 | +type SourceAttributes struct { |
| 80 | + // Downstream connection remote address |
| 81 | + Address string `json:"address,omitempty"` |
| 82 | + // Downstream connection remote port e.g. 8080 |
| 83 | + Port int32 `json:"port,omitempty"` |
| 84 | + // The canonical service name of the peer e.g. “foo.default.svc.cluster.local” |
| 85 | + Service string `json:"service,omitempty"` |
| 86 | + // The labels associated with the peer. These could be pod labels for Kubernetes or tags for VMs. The source of the |
| 87 | + // labels could be an X.509 certificate or other configuration. |
| 88 | + Labels map[string]string `json:"labels,omitempty"` |
| 89 | + // The authenticated identity of this peer. If an X.509 certificate is used to assert the identity in the proxy, this |
| 90 | + // field is sourced from "URI Subject Alternative Names", "DNS Subject Alternate Names" or "Subject" in that order. |
| 91 | + // The format is issuer specific – e.g. SPIFFE format is spiffe://trust-domain/path, Google account format is https://accounts.google.com/{userid}. |
| 92 | + Principal string `json:"principal,omitempty"` |
| 93 | + // The X.509 certificate used to authenticate the identity of this peer. When present, the certificate contents are encoded in URL and PEM format. |
| 94 | + Certificate string `json:"certificate,omitempty"` |
| 95 | +} |
| 96 | + |
| 97 | +type DestinationAttributes struct { |
| 98 | + // Downstream connection local address |
| 99 | + Address string `json:"address,omitempty"` |
| 100 | + // Downstream connection local port e.g. 9090 |
| 101 | + Port int32 `json:"port,omitempty"` |
| 102 | + // The canonical service name of the peer e.g. “foo.default.svc.cluster.local” |
| 103 | + Service string `json:"service,omitempty"` |
| 104 | + // The labels associated with the peer. These could be pod labels for Kubernetes or tags for VMs. The source of the |
| 105 | + // labels could be an X.509 certificate or other configuration. |
| 106 | + Labels map[string]string `json:"labels,omitempty"` |
| 107 | + // The authenticated identity of this peer. If an X.509 certificate is used to assert the identity in the proxy, this |
| 108 | + // field is sourced from "URI Subject Alternative Names", "DNS Subject Alternate Names" or "Subject" in that order. |
| 109 | + // The format is issuer specific – e.g. SPIFFE format is spiffe://trust-domain/path, Google account format is https://accounts.google.com/{userid}. |
| 110 | + Principal string `json:"principal,omitempty"` |
| 111 | + // The X.509 certificate used to authenticate the identity of this peer. When present, the certificate contents are encoded in URL and PEM format. |
| 112 | + Certificate string `json:"certificate,omitempty"` |
| 113 | +} |
| 114 | + |
| 115 | +type AuthAttributes struct { |
| 116 | + // Single resolved identity object, post-identity verification |
| 117 | + Identity any `json:"identity,omitempty"` |
| 118 | + // External metadata fetched |
| 119 | + Metadata map[string]any `json:"metadata,omitempty"` |
| 120 | + // Authorization results resolved by each authorization rule, access granted only |
| 121 | + Authorization map[string]any `json:"authorization,omitempty"` |
| 122 | + // Response objects exported by the auth service post-access granted |
| 123 | + Response map[string]any `json:"response,omitempty"` |
| 124 | + // Response objects returned by the callback requests issued by the auth service |
| 125 | + Callbacks map[string]any `json:"callbacks,omitempty"` |
| 126 | +} |
| 127 | + |
| 128 | +// NewWellKnownAttributes creates a new WellKnownAttributes object from an envoyauth.AttributeContext |
| 129 | +func NewWellKnownAttributes(attributes *envoyauth.AttributeContext, authData map[string]any) *WellKnownAttributes { |
| 130 | + return &WellKnownAttributes{ |
| 131 | + Metadata: attributes.MetadataContext, |
| 132 | + Request: newRequestAttributes(attributes), |
| 133 | + Source: newSourceAttributes(attributes), |
| 134 | + Destination: newDestinationAttributes(attributes), |
| 135 | + Auth: newAuthAttributes(authData), |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +func newRequestAttributes(attributes *envoyauth.AttributeContext) *RequestAttributes { |
| 140 | + request := attributes.GetRequest() |
| 141 | + httpRequest := request.GetHttp() |
| 142 | + urlParsed, _ := url.Parse(httpRequest.Path) |
| 143 | + headers := httpRequest.GetHeaders() |
| 144 | + return &RequestAttributes{ |
| 145 | + Id: httpRequest.Id, |
| 146 | + Time: request.Time, |
| 147 | + Protocol: httpRequest.Protocol, |
| 148 | + Scheme: httpRequest.GetScheme(), |
| 149 | + Host: httpRequest.GetHost(), |
| 150 | + Method: httpRequest.GetMethod(), |
| 151 | + Path: httpRequest.GetPath(), |
| 152 | + URLPath: urlParsed.Path, |
| 153 | + Query: urlParsed.RawQuery, |
| 154 | + Headers: headers, |
| 155 | + Referer: headers["referer"], |
| 156 | + UserAgent: headers["user-agent"], |
| 157 | + Size: httpRequest.GetSize(), |
| 158 | + Body: httpRequest.GetBody(), |
| 159 | + RawBody: httpRequest.GetRawBody(), |
| 160 | + ContextExtensions: attributes.GetContextExtensions(), |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +func newSourceAttributes(attributes *envoyauth.AttributeContext) *SourceAttributes { |
| 165 | + source := attributes.Source |
| 166 | + socketAddress := source.GetAddress().GetSocketAddress() |
| 167 | + return &SourceAttributes{ |
| 168 | + Address: socketAddress.GetAddress(), |
| 169 | + Port: int32(socketAddress.GetPortValue()), |
| 170 | + Service: source.GetService(), |
| 171 | + Labels: source.GetLabels(), |
| 172 | + Principal: source.GetPrincipal(), |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +func newDestinationAttributes(attributes *envoyauth.AttributeContext) *DestinationAttributes { |
| 177 | + destination := attributes.Destination |
| 178 | + socketAddress := destination.GetAddress().GetSocketAddress() |
| 179 | + return &DestinationAttributes{ |
| 180 | + Address: socketAddress.GetAddress(), |
| 181 | + Port: int32(socketAddress.GetPortValue()), |
| 182 | + Service: destination.GetService(), |
| 183 | + Labels: destination.GetLabels(), |
| 184 | + Principal: destination.GetPrincipal(), |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +func newAuthAttributes(authData map[string]interface{}) *AuthAttributes { |
| 189 | + authAttributes := &AuthAttributes{} |
| 190 | + authAttributesValue := reflect.ValueOf(authAttributes).Elem() |
| 191 | + for key, value := range authData { |
| 192 | + fieldValue := authAttributesValue.FieldByName(strings.ToUpper(key[:1]) + key[1:]) |
| 193 | + if fieldValue.IsValid() && fieldValue.CanSet() { |
| 194 | + if value != nil { |
| 195 | + fieldValue.Set(reflect.ValueOf(value)) |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + return authAttributes |
| 200 | +} |
0 commit comments