diff --git a/cli/cmd/lib_realtime_apis.go b/cli/cmd/lib_realtime_apis.go index 9ff3f1a883..de64024619 100644 --- a/cli/cmd/lib_realtime_apis.go +++ b/cli/cmd/lib_realtime_apis.go @@ -167,6 +167,9 @@ func describeModelInput(status *status.Status, predictor *userconfig.Predictor, if predictor.Type == userconfig.TensorFlowPredictorType && !cachingEnabled { apiTFLiveReloadingSummary, err := getAPITFLiveReloadingSummary(apiEndpoint) if err != nil { + if strings.Contains(errors.Message(err), "context deadline exceeded") { + return "error retrieving the models' metadata schema: unable to connect to the API, you either do not have access or the API is too busy" + "\n" + } return "error retrieving the models' metadata schema: " + errors.Message(err) + "\n" } t, err := parseAPITFLiveReloadingSummary(apiTFLiveReloadingSummary) @@ -178,6 +181,9 @@ func describeModelInput(status *status.Status, predictor *userconfig.Predictor, apiModelSummary, err := getAPIModelSummary(apiEndpoint) if err != nil { + if strings.Contains(errors.Message(err), "context deadline exceeded") { + return "error retrieving the models' metadata schema: unable to connect to the API, you either do not have access or the API is too busy" + "\n" + } return "error retrieving the models' metadata schema: " + errors.Message(err) + "\n" } t, err := parseAPIModelSummary(apiModelSummary) @@ -196,7 +202,7 @@ func getModelFromModelID(modelID string) (modelName string, modelVersion int64, func makeRequest(request *http.Request) (http.Header, []byte, error) { client := http.Client{ - Timeout: 600 * time.Second, + Timeout: 5 * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, diff --git a/docs/clusters/management/create.md b/docs/clusters/management/create.md index c8ba400b35..b7c8e613a2 100644 --- a/docs/clusters/management/create.md +++ b/docs/clusters/management/create.md @@ -82,6 +82,12 @@ operator_load_balancer_scheme: internet-facing # - availability_zone: us-west-2b # subnet_id: subnet-0faed05adf6042ab7 +# restrict access to APIs by cidr blocks/ip address ranges +api_load_balancer_cidr_white_list: [0.0.0.0/0] + +# restrict access to the Operator by cidr blocks/ip address ranges +operator_load_balancer_cidr_white_list: [0.0.0.0/0] + # additional tags to assign to AWS resources (all resources will automatically be tagged with cortex.dev/cluster-name: ) tags: # : map of key/value pairs diff --git a/docs/clusters/networking/load-balancers.md b/docs/clusters/networking/load-balancers.md index c67da5e2fd..ad76bc7445 100644 --- a/docs/clusters/networking/load-balancers.md +++ b/docs/clusters/networking/load-balancers.md @@ -2,8 +2,8 @@ ![api architecture diagram](https://user-images.githubusercontent.com/808475/103417256-dd6e9700-4b3e-11eb-901e-90425f1f8fd4.png) -All APIs share a single API load balancer. By default, the API load balancer is public. You can configure your API load balancer to be private by setting `api_load_balancer_scheme: internal` in your cluster configuration file (before creating your cluster). This will make your API only accessible through [VPC Peering](vpc-peering.md). +All APIs share a single API load balancer. By default, the API load balancer is public. You can configure your API load balancer to be private by setting `api_load_balancer_scheme: internal` in your cluster configuration file (before creating your cluster). This will make your API only accessible through [VPC Peering](vpc-peering.md). You can enforce that incoming requests to APIs must originate from specific ip address ranges by specifying `api_load_balancer_cidr_white_list: []` in your cluster configuration. The SSL certificate on the API load balancer is autogenerated during installation using `localhost` as the Common Name (CN). Therefore, clients will need to skip certificate verification when making HTTPS requests to your APIs (e.g. `curl -k https://***`), or make HTTP requests instead (e.g. `curl http://***`). Alternatively, you can enable HTTPS by using a [custom domain](custom-domain.md) or by [creating an API Gateway](https.md) to forward requests to your API load balancer. -There is a separate load balancer for the Cortex operator. By default, the operator load balancer is public. You can configure your operator load balancer to be private by setting `operator_load_balancer_scheme: internal` in your cluster configuration file (before creating your cluster). You can use [VPC Peering](vpc-peering.md) to enable your Cortex CLI to connect to your cluster operator from another VPC. +There is a separate load balancer for the Cortex operator. By default, the operator load balancer is public. You can configure your operator load balancer to be private by setting `operator_load_balancer_scheme: internal` in your cluster configuration file (before creating your cluster). You can use [VPC Peering](vpc-peering.md) to enable your Cortex CLI to connect to your cluster operator from another VPC. You can enforce that incoming requests to the Cortex operator must originate from specific ip address ranges by specifying `operator_load_balancer_cidr_white_list: []` in your cluster configuration. diff --git a/manager/manifests/istio.yaml.j2 b/manager/manifests/istio.yaml.j2 index e17e411266..5aa8c61eb2 100644 --- a/manager/manifests/istio.yaml.j2 +++ b/manager/manifests/istio.yaml.j2 @@ -50,6 +50,9 @@ spec: service: type: LoadBalancer externalTrafficPolicy: Cluster # https://medium.com/pablo-perez/k8s-externaltrafficpolicy-local-or-cluster-40b259a19404, https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies + {% if config.get('operator_load_balancer_cidr_white_list', [])|length > 0 %} + loadBalancerSourceRanges: {{ config['operator_load_balancer_cidr_white_list'] }} + {% endif %} selector: app: operator-istio-gateway istio: ingressgateway-operator @@ -107,6 +110,9 @@ spec: {% endif %} service: type: LoadBalancer + {% if config.get('api_load_balancer_cidr_white_list', [])|length > 0 %} + loadBalancerSourceRanges: {{ config['api_load_balancer_cidr_white_list'] }} + {% endif %} externalTrafficPolicy: Cluster # https://medium.com/pablo-perez/k8s-externaltrafficpolicy-local-or-cluster-40b259a19404, https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies selector: app: apis-istio-gateway diff --git a/pkg/types/clusterconfig/cluster_config.go b/pkg/types/clusterconfig/cluster_config.go index a16fbce6d5..b86e5fe474 100644 --- a/pkg/types/clusterconfig/cluster_config.go +++ b/pkg/types/clusterconfig/cluster_config.go @@ -98,18 +98,20 @@ type CoreConfig struct { } type ManagedConfig struct { - NodeGroups []*NodeGroup `json:"node_groups" yaml:"node_groups"` - Tags map[string]string `json:"tags" yaml:"tags"` - AvailabilityZones []string `json:"availability_zones" yaml:"availability_zones"` - SSLCertificateARN *string `json:"ssl_certificate_arn,omitempty" yaml:"ssl_certificate_arn,omitempty"` - IAMPolicyARNs []string `json:"iam_policy_arns" yaml:"iam_policy_arns"` - SubnetVisibility SubnetVisibility `json:"subnet_visibility" yaml:"subnet_visibility"` - Subnets []*Subnet `json:"subnets,omitempty" yaml:"subnets,omitempty"` - NATGateway NATGateway `json:"nat_gateway" yaml:"nat_gateway"` - APILoadBalancerScheme LoadBalancerScheme `json:"api_load_balancer_scheme" yaml:"api_load_balancer_scheme"` - OperatorLoadBalancerScheme LoadBalancerScheme `json:"operator_load_balancer_scheme" yaml:"operator_load_balancer_scheme"` - VPCCIDR *string `json:"vpc_cidr,omitempty" yaml:"vpc_cidr,omitempty"` - CortexPolicyARN string `json:"cortex_policy_arn" yaml:"cortex_policy_arn"` // this field is not user facing + NodeGroups []*NodeGroup `json:"node_groups" yaml:"node_groups"` + Tags map[string]string `json:"tags" yaml:"tags"` + AvailabilityZones []string `json:"availability_zones" yaml:"availability_zones"` + SSLCertificateARN *string `json:"ssl_certificate_arn,omitempty" yaml:"ssl_certificate_arn,omitempty"` + IAMPolicyARNs []string `json:"iam_policy_arns" yaml:"iam_policy_arns"` + SubnetVisibility SubnetVisibility `json:"subnet_visibility" yaml:"subnet_visibility"` + Subnets []*Subnet `json:"subnets,omitempty" yaml:"subnets,omitempty"` + NATGateway NATGateway `json:"nat_gateway" yaml:"nat_gateway"` + APILoadBalancerScheme LoadBalancerScheme `json:"api_load_balancer_scheme" yaml:"api_load_balancer_scheme"` + OperatorLoadBalancerScheme LoadBalancerScheme `json:"operator_load_balancer_scheme" yaml:"operator_load_balancer_scheme"` + APILoadBalancerCIDRWhiteList []string `json:"api_load_balancer_cidr_white_list,omitempty" yaml:"api_load_balancer_cidr_white_list,omitempty"` + OperatorLoadBalancerCIDRWhiteList []string `json:"operator_load_balancer_cidr_white_list,omitempty" yaml:"operator_load_balancer_cidr_white_list,omitempty"` + VPCCIDR *string `json:"vpc_cidr,omitempty" yaml:"vpc_cidr,omitempty"` + CortexPolicyARN string `json:"cortex_policy_arn" yaml:"cortex_policy_arn"` // this field is not user facing } type NodeGroup struct { @@ -663,6 +665,34 @@ var ManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ return LoadBalancerSchemeFromString(str), nil }, }, + { + StructField: "APILoadBalancerCIDRWhiteList", + StringListValidation: &cr.StringListValidation{ + Validator: func(addresses []string) ([]string, error) { + for i, address := range addresses { + _, err := validateCIDR(address) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("index %d", i)) + } + } + return addresses, nil + }, + }, + }, + { + StructField: "OperatorLoadBalancerCIDRWhiteList", + StringListValidation: &cr.StringListValidation{ + Validator: func(addresses []string) ([]string, error) { + for i, address := range addresses { + _, err := validateCIDR(address) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("index %d", i)) + } + } + return addresses, nil + }, + }, + }, { StructField: "OperatorLoadBalancerScheme", StringValidation: &cr.StringValidation{ @@ -676,7 +706,7 @@ var ManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ { StructField: "VPCCIDR", StringPtrValidation: &cr.StringPtrValidation{ - Validator: validateVPCCIDR, + Validator: validateCIDR, }, }, } @@ -1083,7 +1113,7 @@ func validateBucketName(bucket string) (string, error) { return bucket, nil } -func validateVPCCIDR(cidr string) (string, error) { +func validateCIDR(cidr string) (string, error) { _, _, err := net.ParseCIDR(cidr) if err != nil { return "", errors.WithStack(err)