Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API server NetworkPolicy to support a potential deny all egress #573

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion bundle/manifests/open-liberty.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ metadata:
categories: Application Runtime
certified: "true"
containerImage: icr.io/appcafe/open-liberty-operator:daily
createdAt: "2024-04-11T14:14:26Z"
createdAt: "2024-04-23T19:10:43Z"
description: Deploy and manage containerized Liberty applications
olm.skipRange: '>=0.8.0 <1.3.1'
operators.openshift.io/infrastructure-features: '["disconnected"]'
Expand Down Expand Up @@ -1092,6 +1092,7 @@ spec:
app.kubernetes.io/managed-by: olm
app.kubernetes.io/name: open-liberty-operator
control-plane: controller-manager
openlibertyapplications.apps.openliberty.io/allow-apiserver-access: "true"
spec:
affinity:
nodeAffinity:
Expand Down Expand Up @@ -1297,6 +1298,13 @@ spec:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ spec:
metadata:
labels:
control-plane: controller-manager
openlibertyapplications.apps.openliberty.io/allow-apiserver-access: "true"
annotations:
kubectl.kubernetes.io/default-container: manager

Expand Down
1 change: 1 addition & 0 deletions config/manifests/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ resources:
- ../scorecard
patchesStrategicMerge:
- patches/csvAnnotations.yaml
- patches/apiServerAccessPatch.yaml
patches:
- path: serviceBindingPatch.yaml
target:
Expand Down
14 changes: 14 additions & 0 deletions config/manifests/patches/apiServerAccessPatch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
name: open-liberty.v0.0.0
namespace: placeholder
spec:
install:
spec:
deployments:
- spec:
template:
metadata:
labels:
openlibertyapplications.apps.openliberty.io/allow-apiserver-access: "true"
7 changes: 7 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ rules:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
2 changes: 1 addition & 1 deletion controllers/ltpa_keys_sharing.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func (r *ReconcileOpenLiberty) generateLTPAKeys(instance *olv1.OpenLibertyApplic
err = r.GetClient().Get(context.TODO(), types.NamespacedName{Name: generateLTPAKeysJob.Name, Namespace: generateLTPAKeysJob.Namespace}, generateLTPAKeysJob)
if err != nil && kerrors.IsNotFound(err) {
err = r.CreateOrUpdate(generateLTPAKeysJob, instance, func() error {
lutils.CustomizeLTPAJob(generateLTPAKeysJob, instance, ltpaSecret.Name, ltpaServiceAccountName, ltpaKeysCreationScriptConfigMap.Name)
lutils.CustomizeLTPAJob(generateLTPAKeysJob, instance, ltpaSecret.Name, ltpaServiceAccountName, ltpaKeysCreationScriptConfigMap.Name, OperatorAllowAPIServerAccessLabel)
return nil
})
if err != nil {
Expand Down
105 changes: 103 additions & 2 deletions controllers/openlibertyapplication_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
Expand All @@ -38,8 +39,9 @@ import (
)

const (
OperatorName = "open-liberty-operator"
OperatorShortName = "olo"
OperatorName = "open-liberty-operator"
OperatorShortName = "olo"
OperatorAllowAPIServerAccessLabel = "openlibertyapplications.apps.openliberty.io/allow-apiserver-access"
)

// ReconcileOpenLiberty reconciles an OpenLibertyApplication object
Expand All @@ -58,6 +60,7 @@ const applicationFinalizer = "finalizer.openlibertyapplications.apps.openliberty
// +kubebuilder:rbac:groups=apps,resources=deployments;statefulsets,verbs=get;list;watch;create;update;delete,namespace=open-liberty-operator
// +kubebuilder:rbac:groups=apps,resources=deployments/finalizers;statefulsets,verbs=update,namespace=open-liberty-operator
// +kubebuilder:rbac:groups=core,resources=services;secrets;serviceaccounts;configmaps;persistentvolumeclaims,verbs=get;list;watch;create;update;delete,namespace=open-liberty-operator
// +kubebuilder:rbac:groups=core,resources=endpoints,verbs=get;list,namespace=open-liberty-operator
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;delete,namespace=open-liberty-operator
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=get;list;watch;create;update;delete,namespace=open-liberty-operator
// +kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;delete,namespace=open-liberty-operator
Expand Down Expand Up @@ -377,6 +380,69 @@ func (r *ReconcileOpenLiberty) Reconcile(ctx context.Context, request ctrl.Reque
common.StatusConditionTypeReconciled, instance)
}

// Kube API Server NetworkPolicy (based upon impl. by Martin Smithson)
apiServerNetworkPolicy := &networkingv1.NetworkPolicy{ObjectMeta: metav1.ObjectMeta{
Name: instance.Name + "-egress-dns-and-apiserver-access",
Namespace: instance.Namespace,
}}
err = r.CreateOrUpdate(apiServerNetworkPolicy, instance, func() error {
apiServerNetworkPolicy.Spec.PodSelector = metav1.LabelSelector{
MatchLabels: map[string]string{
OperatorAllowAPIServerAccessLabel: "true",
},
}
apiServerNetworkPolicy.Spec.Egress = make([]networkingv1.NetworkPolicyEgressRule, 0)

var dnsRule networkingv1.NetworkPolicyEgressRule
var usingPermissiveRule bool
// If allowed, add an Egress rule to access the OpenShift DNS or K8s CoreDNS. Otherwise, use a permissive cluster-wide Egress rule.
if r.IsOpenShift() {
usingPermissiveRule, dnsRule = r.getDNSEgressRule(reqLogger, "dns-default", "openshift-dns")
} else {
usingPermissiveRule, dnsRule = r.getDNSEgressRule(reqLogger, "kube-dns", "kube-system")
}
apiServerNetworkPolicy.Spec.Egress = append(apiServerNetworkPolicy.Spec.Egress, dnsRule)

// If allowed, add an Egress rule to access the API server.
// Otherwise, if the OpenShift DNS or K8s CoreDNS Egress rule does not provide permissive cluster-wide access
// and the K8s API server could not be found, use a permissive cluster-wide Egress rule.
if apiServerEndpoints, err := r.getEndpoints("kubernetes", "default"); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, if we can't get DNS info and using permissive rule we can skip this step as optimization. Without DNS it won't be able to resolve hostname in the pod, even if it allows kuberneres service IPs. And most likely if we codun't get DNS svc info we also will fail on this one

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the usingPermissiveRule flag out of the else if block to also exit early when trying the API server Endpoint lookup if DNS check is using permissive rule.

rule := networkingv1.NetworkPolicyEgressRule{}
// Define the port
port := networkingv1.NetworkPolicyPort{}
port.Protocol = &apiServerEndpoints.Subsets[0].Ports[0].Protocol
var portNumber intstr.IntOrString = intstr.FromInt((int)(apiServerEndpoints.Subsets[0].Ports[0].Port))
port.Port = &portNumber
rule.Ports = append(rule.Ports, port)

// Add the endpoint address as ipBlock entries
for _, endpoint := range apiServerEndpoints.Subsets {
for _, address := range endpoint.Addresses {
peer := networkingv1.NetworkPolicyPeer{}
ipBlock := networkingv1.IPBlock{}
ipBlock.CIDR = address.IP + "/32"

peer.IPBlock = &ipBlock
rule.To = append(rule.To, peer)
}
}
apiServerNetworkPolicy.Spec.Egress = append(apiServerNetworkPolicy.Spec.Egress, rule)
reqLogger.Info("Found endpoints for kubernetes service in the default namespace")
} else if !usingPermissiveRule {
rule := networkingv1.NetworkPolicyEgressRule{}
apiServerNetworkPolicy.Spec.Egress = append(apiServerNetworkPolicy.Spec.Egress, rule)
reqLogger.Info("Found endpoints for kubernetes service in the default namespace")
}
apiServerNetworkPolicy.Labels = ba.GetLabels()
apiServerNetworkPolicy.Annotations = oputils.MergeMaps(apiServerNetworkPolicy.Annotations, ba.GetAnnotations())
apiServerNetworkPolicy.Spec.PolicyTypes = []networkingv1.PolicyType{networkingv1.PolicyTypeEgress}
return nil
})
if err != nil {
reqLogger.Error(err, "Failed to reconcile API server network policy")
return r.ManageError(err, common.StatusConditionTypeReconciled, instance)
}

networkPolicy := &networkingv1.NetworkPolicy{ObjectMeta: defaultMeta}
if np := instance.Spec.NetworkPolicy; np == nil || np != nil && !np.IsDisabled() {
err = r.CreateOrUpdate(networkPolicy, instance, func() error {
Expand Down Expand Up @@ -850,3 +916,38 @@ func (r *ReconcileOpenLiberty) deletePVC(reqLogger logr.Logger, pvcName string,
}
}
}

func (r *ReconcileOpenLiberty) getEndpoints(serviceName string, namespace string) (*corev1.Endpoints, error) {
endpoints := &corev1.Endpoints{}
if err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: serviceName, Namespace: namespace}, endpoints); err != nil {
return nil, err
} else {
return endpoints, nil
}
}

func (r *ReconcileOpenLiberty) getDNSEgressRule(reqLogger logr.Logger, endpointsName string, endpointsNamespace string) (bool, networkingv1.NetworkPolicyEgressRule) {
dnsRule := networkingv1.NetworkPolicyEgressRule{}
if dnsEndpoints, err := r.getEndpoints(endpointsName, endpointsNamespace); err == nil {
if endpointPort := lutils.GetEndpointPortByName(&dnsEndpoints.Subsets[0].Ports, "dns"); endpointPort != nil {
dnsRule.Ports = append(dnsRule.Ports, lutils.CreateNetworkPolicyPortFromEndpointPort(endpointPort))
}
if endpointPort := lutils.GetEndpointPortByName(&dnsEndpoints.Subsets[0].Ports, "dns-tcp"); endpointPort != nil {
dnsRule.Ports = append(dnsRule.Ports, lutils.CreateNetworkPolicyPortFromEndpointPort(endpointPort))
}
peer := networkingv1.NetworkPolicyPeer{}
peer.NamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"kubernetes.io/metadata.name": endpointsNamespace,
},
}
dnsRule.To = append(dnsRule.To, peer)
reqLogger.Info("Found endpoints for " + endpointsName + " service in the " + endpointsNamespace + " namespace")
return false, dnsRule
}
// use permissive rule
// egress:
// - {}
reqLogger.Info("Failed to retrieve endpoints for " + endpointsName + " service in the " + endpointsNamespace + " namespace. Using more permissive rule.")
return true, dnsRule
}
8 changes: 8 additions & 0 deletions internal/deploy/kubectl/openliberty-app-operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ rules:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down Expand Up @@ -297,6 +304,7 @@ spec:
app.kubernetes.io/instance: open-liberty-operator
app.kubernetes.io/name: open-liberty-operator
control-plane: controller-manager
openlibertyapplications.apps.openliberty.io/allow-apiserver-access: "true"
spec:
affinity:
nodeAffinity:
Expand Down
7 changes: 7 additions & 0 deletions internal/deploy/kubectl/openliberty-app-rbac-watch-all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ rules:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ rules:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ spec:
app.kubernetes.io/instance: open-liberty-operator
app.kubernetes.io/name: open-liberty-operator
control-plane: controller-manager
openlibertyapplications.apps.openliberty.io/allow-apiserver-access: "true"
spec:
affinity:
nodeAffinity:
Expand Down
7 changes: 7 additions & 0 deletions internal/deploy/kustomize/daily/base/open-liberty-roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ rules:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ rules:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ rules:
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
24 changes: 23 additions & 1 deletion utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"strconv"
"strings"

networkingv1 "k8s.io/api/networking/v1"

olv1 "github.com/OpenLiberty/open-liberty-operator/api/v1"
rcoutils "github.com/application-stacks/runtime-component-operator/utils"
routev1 "github.com/openshift/api/route/v1"
Expand All @@ -18,6 +20,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -609,9 +612,11 @@ func IsLTPAJobConfigurationOutdated(job *v1.Job, appLeaderInstance *olv1.OpenLib
return false
}

func CustomizeLTPAJob(job *v1.Job, la *olv1.OpenLibertyApplication, ltpaSecretName string, serviceAccountName string, ltpaScriptName string) {
func CustomizeLTPAJob(job *v1.Job, la *olv1.OpenLibertyApplication, ltpaSecretName string, serviceAccountName string, ltpaScriptName string, allowAPIServerAccessLabel string) {
encodingType := "aes" // the password encoding type for securityUtility (one of "xor", "aes", or "hash")
job.Spec.Template.ObjectMeta.Name = "liberty"
// Enable NetworkPolicy Egress access to Kube API Server
job.Spec.Template.Labels = rcoutils.MergeMaps(job.Spec.Template.Labels, map[string]string{allowAPIServerAccessLabel: "true"})
job.Spec.Template.Spec.Containers = []corev1.Container{
{
Name: job.Spec.Template.ObjectMeta.Name,
Expand Down Expand Up @@ -678,3 +683,20 @@ func GetRequiredLabels(name string, instance string) map[string]string {
requiredLabels["app.kubernetes.io/managed-by"] = "open-liberty-operator"
return requiredLabels
}

func GetEndpointPortByName(endpointPorts *[]corev1.EndpointPort, name string) *corev1.EndpointPort {
for _, endpointPort := range *endpointPorts {
if endpointPort.Name == name {
return &endpointPort
}
}
return nil
}

func CreateNetworkPolicyPortFromEndpointPort(endpointPort *corev1.EndpointPort) networkingv1.NetworkPolicyPort {
port := networkingv1.NetworkPolicyPort{}
port.Protocol = &endpointPort.Protocol
var portNumber intstr.IntOrString = intstr.FromInt((int)(endpointPort.Port))
port.Port = &portNumber
return port
}