blob: d6b6f8d56b0ac94b8d5426b5381196d29d4f10a9 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed 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 security
import (
"fmt"
"net"
"net/url"
"strconv"
"strings"
"unicode"
)
import (
"github.com/hashicorp/go-multierror"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
// JwksInfo provides values resulting from parsing a jwks URI.
type JwksInfo struct {
Hostname host.Name
Scheme string
Port int
UseSSL bool
}
const (
attrRequestHeader = "request.headers" // header name is surrounded by brackets, e.g. "request.headers[User-Agent]".
attrSrcIP = "source.ip" // supports both single ip and cidr, e.g. "10.1.2.3" or "10.1.0.0/16".
attrRemoteIP = "remote.ip" // original client ip determined from x-forwarded-for or proxy protocol.
attrSrcNamespace = "source.namespace" // e.g. "default".
attrSrcPrincipal = "source.principal" // source identity, e,g, "cluster.local/ns/default/sa/productpage".
attrRequestPrincipal = "request.auth.principal" // authenticated principal of the request.
attrRequestAudiences = "request.auth.audiences" // intended audience(s) for this authentication information.
attrRequestPresenter = "request.auth.presenter" // authorized presenter of the credential.
attrRequestClaims = "request.auth.claims" // claim name is surrounded by brackets, e.g. "request.auth.claims[iss]".
attrDestIP = "destination.ip" // supports both single ip and cidr, e.g. "10.1.2.3" or "10.1.0.0/16".
attrDestPort = "destination.port" // must be in the range [0, 65535].
attrDestLabel = "destination.labels" // label name is surrounded by brackets, e.g. "destination.labels[version]".
attrDestName = "destination.name" // short service name, e.g. "productpage".
attrDestNamespace = "destination.namespace" // e.g. "default".
attrDestUser = "destination.user" // service account, e.g. "bookinfo-productpage".
attrConnSNI = "connection.sni" // server name indication, e.g. "www.example.com".
attrExperimental = "experimental.envoy.filters."
)
// ParseJwksURI parses the input URI and returns the corresponding hostname, port, and whether SSL is used.
// URI must start with "http://" or "https://", which corresponding to "http" or "https" scheme.
// Port number is extracted from URI if available (i.e from postfix :<port>, eg. ":80"), or assigned
// to a default value based on URI scheme (80 for http and 443 for https).
// Port name is set to URI scheme value.
func ParseJwksURI(jwksURI string) (JwksInfo, error) {
u, err := url.Parse(jwksURI)
if err != nil {
return JwksInfo{}, err
}
info := JwksInfo{}
switch u.Scheme {
case "http":
info.UseSSL = false
info.Port = 80
case "https":
info.UseSSL = true
info.Port = 443
default:
return JwksInfo{}, fmt.Errorf("URI scheme %q is not supported", u.Scheme)
}
if u.Port() != "" {
info.Port, err = strconv.Atoi(u.Port())
if err != nil {
return JwksInfo{}, err
}
}
info.Hostname = host.Name(u.Hostname())
info.Scheme = u.Scheme
return info, nil
}
func CheckEmptyValues(key string, values []string) error {
for _, value := range values {
if value == "" {
return fmt.Errorf("empty value not allowed, found in %s", key)
}
}
return nil
}
func ValidateAttribute(key string, values []string) error {
if err := CheckEmptyValues(key, values); err != nil {
return err
}
switch {
case hasPrefix(key, attrRequestHeader):
return validateMapKey(key)
case isEqual(key, attrSrcIP):
return ValidateIPs(values)
case isEqual(key, attrRemoteIP):
return ValidateIPs(values)
case isEqual(key, attrSrcNamespace):
case isEqual(key, attrSrcPrincipal):
case isEqual(key, attrRequestPrincipal):
case isEqual(key, attrRequestAudiences):
case isEqual(key, attrRequestPresenter):
case hasPrefix(key, attrRequestClaims):
return validateMapKey(key)
case isEqual(key, attrDestIP):
return ValidateIPs(values)
case isEqual(key, attrDestPort):
return ValidatePorts(values)
case isEqual(key, attrConnSNI):
case hasPrefix(key, attrExperimental):
return validateMapKey(key)
case isEqual(key, attrDestNamespace):
return fmt.Errorf("attribute %s is replaced by the metadata.namespace", key)
case hasPrefix(key, attrDestLabel):
return fmt.Errorf("attribute %s is replaced by the workload selector", key)
case isEqual(key, attrDestName, attrDestUser):
return fmt.Errorf("deprecated attribute %s: only supported in v1alpha1", key)
default:
return fmt.Errorf("unknown attribute: %s", key)
}
return nil
}
func isEqual(key string, values ...string) bool {
for _, v := range values {
if key == v {
return true
}
}
return false
}
func hasPrefix(key string, prefix string) bool {
return strings.HasPrefix(key, prefix)
}
func ValidateIPs(ips []string) error {
var errs *multierror.Error
for _, v := range ips {
if strings.Contains(v, "/") {
if _, _, err := net.ParseCIDR(v); err != nil {
errs = multierror.Append(errs, fmt.Errorf("bad CIDR range (%s): %v", v, err))
}
} else {
if ip := net.ParseIP(v); ip == nil {
errs = multierror.Append(errs, fmt.Errorf("bad IP address (%s)", v))
}
}
}
return errs.ErrorOrNil()
}
func ValidatePorts(ports []string) error {
var errs *multierror.Error
for _, port := range ports {
p, err := strconv.ParseUint(port, 10, 32)
if err != nil || p > 65535 {
errs = multierror.Append(errs, fmt.Errorf("bad port (%s): %v", port, err))
}
}
return errs.ErrorOrNil()
}
func validateMapKey(key string) error {
open := strings.Index(key, "[")
if strings.HasSuffix(key, "]") && open > 0 && open < len(key)-2 {
return nil
}
return fmt.Errorf("bad key (%s): should have format a[b]", key)
}
// ValidCipherSuites contains a list of all ciphers supported in Gateway.server.tls.cipherSuites
// Extracted from: `bssl ciphers -openssl-name ALL | rg -v PSK`
var ValidCipherSuites = sets.New(
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305",
"ECDHE-ECDSA-AES128-SHA",
"ECDHE-RSA-AES128-SHA",
"ECDHE-ECDSA-AES256-SHA",
"ECDHE-RSA-AES256-SHA",
"AES128-GCM-SHA256",
"AES256-GCM-SHA384",
"AES128-SHA",
"AES256-SHA",
"DES-CBC3-SHA",
)
func IsValidCipherSuite(cs string) bool {
if cs == "" || cs == "ALL" {
return true
}
if !unicode.IsNumber(rune(cs[0])) && !unicode.IsLetter(rune(cs[0])) {
// Not all of these are correct, but this is needed to support advanced cases like - and + operators
// without needing to parse the full expression
return true
}
return ValidCipherSuites.Contains(cs)
}