...

Text file src/github.com/linkerd/linkerd2/policy-test/tests/e2e_authorization_policy.rs

Documentation: github.com/linkerd/linkerd2/policy-test/tests

     1use kube::ResourceExt;
     2use linkerd_policy_controller_k8s_api::{
     3    self as k8s,
     4    policy::{LocalTargetRef, NamespacedTargetRef},
     5};
     6use linkerd_policy_test::{
     7    await_condition, create, create_ready_pod, curl, endpoints_ready, web, with_temp_ns,
     8    LinkerdInject,
     9};
    10
    11#[tokio::test(flavor = "current_thread")]
    12async fn meshtls() {
    13    with_temp_ns(|client, ns| async move {
    14        // First create all of the policies we'll need so that the web pod
    15        // starts up with the correct policy (to prevent races).
    16        //
    17        // The policy requires that all connections are authenticated with MeshTLS.
    18        let (srv, all_mtls) = tokio::join!(
    19            create(&client, web::server(&ns)),
    20            create(&client, all_authenticated(&ns))
    21        );
    22        create(
    23            &client,
    24            authz_policy(
    25                &ns,
    26                "web",
    27                LocalTargetRef::from_resource(&srv),
    28                Some(NamespacedTargetRef::from_resource(&all_mtls)),
    29            ),
    30        )
    31        .await;
    32
    33        // Create the web pod and wait for it to be ready.
    34        tokio::join!(
    35            create(&client, web::service(&ns)),
    36            create_ready_pod(&client, web::pod(&ns))
    37        );
    38
    39        let curl = curl::Runner::init(&client, &ns).await;
    40        let (injected, uninjected) = tokio::join!(
    41            curl.run("curl-injected", "http://web", LinkerdInject::Enabled),
    42            curl.run("curl-uninjected", "http://web", LinkerdInject::Disabled),
    43        );
    44        let (injected_status, uninjected_status) =
    45            tokio::join!(injected.exit_code(), uninjected.exit_code());
    46        assert_eq!(
    47            injected_status, 0,
    48            "uninjected curl must fail to contact web"
    49        );
    50        assert_ne!(uninjected_status, 0, "injected curl must contact web");
    51    })
    52    .await;
    53}
    54
    55#[tokio::test(flavor = "current_thread")]
    56async fn targets_route() {
    57    with_temp_ns(|client, ns| async move {
    58        // First create all of the policies we'll need so that the web pod
    59        // starts up with the correct policy (to prevent races).
    60        //
    61        // The policy requires that all connections are authenticated with MeshTLS.
    62        let (srv, all_mtls) = tokio::join!(
    63            create(&client, web::server(&ns)),
    64            create(&client, all_authenticated(&ns)),
    65        );
    66        // Create a route which matches the /allowed path.
    67        let (root_route, _roux_route) = tokio::join!(
    68            create(&client, http_route("root", &ns, &srv.name_unchecked(), "/"),),
    69            create(
    70                &client,
    71                http_route("roux", &ns, &srv.name_unchecked(), "/roux")
    72            )
    73        );
    74        // Create a policy which allows all authenticated clients
    75        create(
    76            &client,
    77            authz_policy(
    78                &ns,
    79                "root-authz",
    80                LocalTargetRef::from_resource(&root_route),
    81                Some(NamespacedTargetRef::from_resource(&all_mtls)),
    82            ),
    83        )
    84        .await;
    85
    86        // Create the web pod and wait for it to be ready.
    87        tokio::join!(
    88            create(&client, web::service(&ns)),
    89            create_ready_pod(&client, web::pod(&ns))
    90        );
    91
    92        let curl = curl::Runner::init(&client, &ns).await;
    93
    94        let (allowed, no_route, unauth, no_authz) = tokio::join!(
    95            curl.run("curl-allowed", "http://web/", LinkerdInject::Enabled),
    96            curl.run(
    97                "curl-no-route",
    98                "http://web/noroute",
    99                LinkerdInject::Enabled
   100            ),
   101            curl.run("curl-unauth", "http://web/", LinkerdInject::Disabled),
   102            curl.run(
   103                "curl-route-without-authz",
   104                "http://web/roux",
   105                LinkerdInject::Enabled
   106            ),
   107        );
   108        let (allowed_status, no_route_status, unauth_status, no_authz_status) = tokio::join!(
   109            allowed.http_status_code(),
   110            no_route.http_status_code(),
   111            unauth.http_status_code(),
   112            no_authz.http_status_code(),
   113        );
   114        assert!(
   115            allowed_status.is_success(),
   116            "curling allowed route must contact web"
   117        );
   118        assert_eq!(
   119            no_route_status,
   120            hyper::StatusCode::NOT_FOUND,
   121            "curl which does not match route must not contact web"
   122        );
   123        assert_eq!(
   124            unauth_status,
   125            hyper::StatusCode::FORBIDDEN,
   126            "curl which is not authenticated must not contact web"
   127        );
   128        assert_eq!(
   129            no_authz_status,
   130            hyper::StatusCode::FORBIDDEN,
   131            "curl to route with no authorizations must not contact web"
   132        );
   133
   134        // Create a policy which allows all authenticated clients to access the server.
   135        create(
   136            &client,
   137            authz_policy(
   138                &ns,
   139                "server-authz",
   140                LocalTargetRef::from_resource(&srv),
   141                Some(NamespacedTargetRef::from_resource(&all_mtls)),
   142            ),
   143        )
   144        .await;
   145
   146        // Curl a route which doesn't have any authz, but its server does have
   147        // an authz.
   148        let route_with_server_authz_status = curl
   149            .run(
   150                "curl-route-with-server-authz",
   151                "http://web/roux",
   152                LinkerdInject::Enabled,
   153            )
   154            .await
   155            .http_status_code()
   156            .await;
   157
   158        assert!(
   159            route_with_server_authz_status.is_success(),
   160            "curl to route with no authorizations on server with authorizations must contact web"
   161        );
   162    })
   163    .await;
   164}
   165
   166#[tokio::test(flavor = "current_thread")]
   167async fn targets_namespace() {
   168    with_temp_ns(|client, ns| async move {
   169        // First create all of the policies we'll need so that the web pod
   170        // starts up with the correct policy (to prevent races).
   171        //
   172        // The policy requires that all connections are authenticated with MeshTLS.
   173        let (_srv, all_mtls) = tokio::join!(
   174            create(&client, web::server(&ns)),
   175            create(&client, all_authenticated(&ns))
   176        );
   177        create(
   178            &client,
   179            authz_policy(
   180                &ns,
   181                "web",
   182                LocalTargetRef {
   183                    group: None,
   184                    kind: "Namespace".to_string(),
   185                    name: ns.clone(),
   186                },
   187                Some(NamespacedTargetRef::from_resource(&all_mtls)),
   188            ),
   189        )
   190        .await;
   191
   192        // Create the web pod and wait for it to be ready.
   193        tokio::join!(
   194            create(&client, web::service(&ns)),
   195            create_ready_pod(&client, web::pod(&ns))
   196        );
   197
   198        let curl = curl::Runner::init(&client, &ns).await;
   199        let (injected, uninjected) = tokio::join!(
   200            curl.run("curl-injected", "http://web", LinkerdInject::Enabled),
   201            curl.run("curl-uninjected", "http://web", LinkerdInject::Disabled),
   202        );
   203        let (injected_status, uninjected_status) =
   204            tokio::join!(injected.exit_code(), uninjected.exit_code());
   205        assert_eq!(injected_status, 0, "injected curl must contact web");
   206        assert_ne!(
   207            uninjected_status, 0,
   208            "uninjected curl must fail to contact web"
   209        );
   210    })
   211    .await;
   212}
   213
   214#[tokio::test(flavor = "current_thread")]
   215async fn meshtls_namespace() {
   216    with_temp_ns(|client, ns| async move {
   217        // First create all of the policies we'll need so that the web pod
   218        // starts up with the correct policy (to prevent races).
   219        //
   220        // The policy requires that all connections are authenticated with MeshTLS
   221        // and come from service accounts in the given namespace.
   222        let (srv, mtls_ns) = tokio::join!(
   223            create(&client, web::server(&ns)),
   224            create(&client, ns_authenticated(&ns))
   225        );
   226        create(
   227            &client,
   228            authz_policy(
   229                &ns,
   230                "web",
   231                LocalTargetRef::from_resource(&srv),
   232                Some(NamespacedTargetRef::from_resource(&mtls_ns)),
   233            ),
   234        )
   235        .await;
   236
   237        // Create the web pod and wait for it to be ready.
   238        tokio::join!(
   239            create(&client, web::service(&ns)),
   240            create_ready_pod(&client, web::pod(&ns))
   241        );
   242
   243        let curl = curl::Runner::init(&client, &ns).await;
   244        let (injected, uninjected) = tokio::join!(
   245            curl.run("curl-injected", "http://web", LinkerdInject::Enabled),
   246            curl.run("curl-uninjected", "http://web", LinkerdInject::Disabled),
   247        );
   248        let (injected_status, uninjected_status) =
   249            tokio::join!(injected.exit_code(), uninjected.exit_code());
   250        assert_eq!(injected_status, 0, "injected curl must contact web");
   251        assert_ne!(
   252            uninjected_status, 0,
   253            "uninjected curl must fail to contact web"
   254        );
   255    })
   256    .await;
   257}
   258
   259#[tokio::test(flavor = "current_thread")]
   260async fn network() {
   261    // In order to test the network policy, we need to create the client pod
   262    // before creating the authorization policy. To avoid races, we do this by
   263    // creating a `curl-lock` configmap that prevents curl from actually being
   264    // executed. Once web is running with the correct policy, the configmap is
   265    // deleted to unblock the curl pods.
   266    with_temp_ns(|client, ns| async move {
   267        let curl = curl::Runner::init(&client, &ns).await;
   268        curl.create_lock().await;
   269
   270        // Create a curl pod and wait for it to get an IP.
   271        let blessed = curl
   272            .run("curl-blessed", "http://web", LinkerdInject::Disabled)
   273            .await;
   274        let blessed_ip = blessed.ip().await;
   275        tracing::debug!(curl.blessed.ip = %blessed_ip);
   276
   277        // Once we know the IP of the (blocked) pod, create an web
   278        // authorization policy that permits connections from this pod.
   279        let (srv, allow_ips) = tokio::join!(
   280            create(&client, web::server(&ns)),
   281            create(&client, allow_ips(&ns, Some(blessed_ip)))
   282        );
   283        create(
   284            &client,
   285            authz_policy(
   286                &ns,
   287                "web",
   288                LocalTargetRef::from_resource(&srv),
   289                Some(NamespacedTargetRef::from_resource(&allow_ips)),
   290            ),
   291        )
   292        .await;
   293
   294        // Start web with the policy.
   295        tokio::join!(
   296            create(&client, web::service(&ns)),
   297            create_ready_pod(&client, web::pod(&ns))
   298        );
   299
   300        await_condition(&client, &ns, "web", endpoints_ready).await;
   301
   302        // Once the web pod is ready, delete the `curl-lock` configmap to
   303        // unblock curl from running.
   304        curl.delete_lock().await;
   305        tracing::info!("unblocked curl");
   306
   307        // The blessed pod should be able to connect to the web pod.
   308        let status = blessed.exit_code().await;
   309        assert_eq!(status, 0, "blessed curl pod must succeed");
   310
   311        // Create another curl pod that is not included in the authorization. It
   312        // should fail to connect to the web pod.
   313        let status = curl
   314            .run("curl-cursed", "http://web", LinkerdInject::Disabled)
   315            .await
   316            .exit_code()
   317            .await;
   318        assert_ne!(status, 0, "cursed curl pod must fail");
   319    })
   320    .await;
   321}
   322
   323#[tokio::test(flavor = "current_thread")]
   324async fn both() {
   325    // In order to test the network policy, we need to create the client pod
   326    // before creating the authorization policy. To avoid races, we do this by
   327    // creating a `curl-lock` configmap that prevents curl from actually being
   328    // executed. Once web is running with the correct policy, the configmap is
   329    // deleted to unblock the curl pods.
   330    with_temp_ns(|client, ns| async move {
   331        let curl = curl::Runner::init(&client, &ns).await;
   332        curl.create_lock().await;
   333
   334        let (blessed_injected, blessed_uninjected) = tokio::join!(
   335            curl.run(
   336                "curl-blessed-injected",
   337                "http://web",
   338                LinkerdInject::Enabled,
   339            ),
   340            curl.run(
   341                "curl-blessed-uninjected",
   342                "http://web",
   343                LinkerdInject::Disabled,
   344            )
   345        );
   346        let (blessed_injected_ip, blessed_uninjected_ip) =
   347            tokio::join!(blessed_injected.ip(), blessed_uninjected.ip(),);
   348        tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);
   349        tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);
   350
   351        // Once we know the IP of the (blocked) pod, create an web
   352        // authorization policy that permits connections from this pod.
   353        let (srv, allow_ips, all_mtls) = tokio::join!(
   354            create(&client, web::server(&ns)),
   355            create(
   356                &client,
   357                allow_ips(&ns, vec![blessed_injected_ip, blessed_uninjected_ip]),
   358            ),
   359            create(&client, all_authenticated(&ns))
   360        );
   361        create(
   362            &client,
   363            authz_policy(
   364                &ns,
   365                "web",
   366                LocalTargetRef::from_resource(&srv),
   367                vec![
   368                    NamespacedTargetRef::from_resource(&allow_ips),
   369                    NamespacedTargetRef::from_resource(&all_mtls),
   370                ],
   371            ),
   372        )
   373        .await;
   374
   375        // Start web with the policy.
   376        tokio::join!(
   377            create(&client, web::service(&ns)),
   378            create_ready_pod(&client, web::pod(&ns))
   379        );
   380
   381        await_condition(&client, &ns, "web", endpoints_ready).await;
   382
   383        // Once the web pod is ready, delete the `curl-lock` configmap to
   384        // unblock curl from running.
   385        curl.delete_lock().await;
   386        tracing::info!("unblocked curl");
   387
   388        let (blessed_injected_status, blessed_uninjected_status) =
   389            tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());
   390        // The blessed and injected pod should be able to connect to the web pod.
   391        assert_eq!(
   392            blessed_injected_status, 0,
   393            "blessed injected curl pod must succeed"
   394        );
   395        // The blessed and uninjected pod should NOT be able to connect to the web pod.
   396        assert_ne!(
   397            blessed_uninjected_status, 0,
   398            "blessed uninjected curl pod must NOT succeed"
   399        );
   400
   401        let (cursed_injected, cursed_uninjected) = tokio::join!(
   402            curl.run("curl-cursed-injected", "http://web", LinkerdInject::Enabled,),
   403            curl.run(
   404                "curl-cursed-uninjected",
   405                "http://web",
   406                LinkerdInject::Disabled,
   407            )
   408        );
   409        let (cursed_injected_status, cursed_uninjected_status) =
   410            tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code(),);
   411        assert_ne!(
   412            cursed_injected_status, 0,
   413            "cursed injected curl pod must fail"
   414        );
   415        assert_ne!(
   416            cursed_uninjected_status, 0,
   417            "cursed uninjected curl pod must fail"
   418        );
   419    })
   420    .await;
   421}
   422
   423#[tokio::test(flavor = "current_thread")]
   424async fn either() {
   425    // In order to test the network policy, we need to create the client pod
   426    // before creating the authorization policy. To avoid races, we do this by
   427    // creating a `curl-lock` configmap that prevents curl from actually being
   428    // executed. Once web is running with the correct policy, the configmap is
   429    // deleted to unblock the curl pods.
   430    with_temp_ns(|client, ns| async move {
   431        let curl = curl::Runner::init(&client, &ns).await;
   432        curl.create_lock().await;
   433
   434        let (blessed_injected, blessed_uninjected) = tokio::join!(
   435            curl.run(
   436                "curl-blessed-injected",
   437                "http://web",
   438                LinkerdInject::Enabled,
   439            ),
   440            curl.run(
   441                "curl-blessed-uninjected",
   442                "http://web",
   443                LinkerdInject::Disabled,
   444            )
   445        );
   446        let (blessed_injected_ip, blessed_uninjected_ip) =
   447            tokio::join!(blessed_injected.ip(), blessed_uninjected.ip());
   448        tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);
   449        tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);
   450
   451        // Once we know the IP of the (blocked) pod, create an web
   452        // authorization policy that permits connections from this pod.
   453        let (srv, allow_ips, all_mtls) = tokio::join!(
   454            create(&client, web::server(&ns)),
   455            create(&client, allow_ips(&ns, vec![blessed_uninjected_ip])),
   456            create(&client, all_authenticated(&ns))
   457        );
   458        tokio::join!(
   459            create(
   460                &client,
   461                authz_policy(
   462                    &ns,
   463                    "web-from-ip",
   464                    LocalTargetRef::from_resource(&srv),
   465                    vec![NamespacedTargetRef::from_resource(&allow_ips)],
   466                ),
   467            ),
   468            create(
   469                &client,
   470                authz_policy(
   471                    &ns,
   472                    "web-from-id",
   473                    LocalTargetRef::from_resource(&srv),
   474                    vec![NamespacedTargetRef::from_resource(&all_mtls)],
   475                ),
   476            )
   477        );
   478
   479        // Start web with the policy.
   480        tokio::join!(
   481            create(&client, web::service(&ns)),
   482            create_ready_pod(&client, web::pod(&ns)),
   483        );
   484
   485        await_condition(&client, &ns, "web", endpoints_ready).await;
   486
   487        // Once the web pod is ready, delete the `curl-lock` configmap to
   488        // unblock curl from running.
   489        curl.delete_lock().await;
   490        tracing::info!("unblocked curl");
   491
   492        let (blessed_injected_status, blessed_uninjected_status) =
   493            tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());
   494        // The blessed and injected pod should be able to connect to the web pod.
   495        assert_eq!(
   496            blessed_injected_status, 0,
   497            "blessed injected curl pod must succeed"
   498        );
   499        // The blessed and uninjected pod should NOT be able to connect to the web pod.
   500        assert_eq!(
   501            blessed_uninjected_status, 0,
   502            "blessed uninjected curl pod must succeed"
   503        );
   504
   505        let (cursed_injected, cursed_uninjected) = tokio::join!(
   506            curl.run("curl-cursed-injected", "http://web", LinkerdInject::Enabled,),
   507            curl.run(
   508                "curl-cursed-uninjected",
   509                "http://web",
   510                LinkerdInject::Disabled,
   511            ),
   512        );
   513        let (cursed_injected_status, cursed_uninjected_status) =
   514            tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code());
   515        assert_eq!(
   516            cursed_injected_status, 0,
   517            "cursed injected curl pod must succeed"
   518        );
   519        assert_ne!(
   520            cursed_uninjected_status, 0,
   521            "cursed uninjected curl pod must fail"
   522        );
   523    })
   524    .await;
   525}
   526
   527#[tokio::test(flavor = "current_thread")]
   528async fn empty_authentications() {
   529    with_temp_ns(|client, ns| async move {
   530        // Create a policy that does not require any authentications.
   531        let srv = create(&client, web::server(&ns)).await;
   532        create(
   533            &client,
   534            authz_policy(&ns, "web", LocalTargetRef::from_resource(&srv), None),
   535        )
   536        .await;
   537
   538        // Create the web pod and wait for it to be ready.
   539        tokio::join!(
   540            create(&client, web::service(&ns)),
   541            create_ready_pod(&client, web::pod(&ns))
   542        );
   543
   544        // All requests should work.
   545        let curl = curl::Runner::init(&client, &ns).await;
   546        let (injected, uninjected) = tokio::join!(
   547            curl.run("curl-injected", "http://web", LinkerdInject::Enabled),
   548            curl.run("curl-uninjected", "http://web", LinkerdInject::Disabled),
   549        );
   550        let (injected_status, uninjected_status) =
   551            tokio::join!(injected.exit_code(), uninjected.exit_code());
   552        assert_eq!(injected_status, 0, "injected curl must contact web");
   553        assert_eq!(uninjected_status, 0, "uninjected curl must contact web");
   554    })
   555    .await;
   556}
   557
   558// === helpers ===
   559
   560fn authz_policy(
   561    ns: &str,
   562    name: &str,
   563    target: LocalTargetRef,
   564    authns: impl IntoIterator<Item = NamespacedTargetRef>,
   565) -> k8s::policy::AuthorizationPolicy {
   566    k8s::policy::AuthorizationPolicy {
   567        metadata: k8s::ObjectMeta {
   568            namespace: Some(ns.to_string()),
   569            name: Some(name.to_string()),
   570            ..Default::default()
   571        },
   572        spec: k8s::policy::AuthorizationPolicySpec {
   573            target_ref: target,
   574            required_authentication_refs: authns.into_iter().collect(),
   575        },
   576    }
   577}
   578
   579fn all_authenticated(ns: &str) -> k8s::policy::MeshTLSAuthentication {
   580    k8s::policy::MeshTLSAuthentication {
   581        metadata: k8s::ObjectMeta {
   582            namespace: Some(ns.to_string()),
   583            name: Some("all-authenticated".to_string()),
   584            ..Default::default()
   585        },
   586        spec: k8s::policy::MeshTLSAuthenticationSpec {
   587            identity_refs: None,
   588            identities: Some(vec!["*".to_string()]),
   589        },
   590    }
   591}
   592
   593fn ns_authenticated(ns: &str) -> k8s::policy::MeshTLSAuthentication {
   594    k8s::policy::MeshTLSAuthentication {
   595        metadata: k8s::ObjectMeta {
   596            namespace: Some(ns.to_string()),
   597            name: Some("all-authenticated".to_string()),
   598            ..Default::default()
   599        },
   600        spec: k8s::policy::MeshTLSAuthenticationSpec {
   601            identity_refs: Some(vec![NamespacedTargetRef {
   602                group: None,
   603                kind: "Namespace".to_string(),
   604                name: ns.to_string(),
   605                namespace: None,
   606            }]),
   607            identities: None,
   608        },
   609    }
   610}
   611
   612fn allow_ips(
   613    ns: &str,
   614    ips: impl IntoIterator<Item = std::net::IpAddr>,
   615) -> k8s::policy::NetworkAuthentication {
   616    k8s::policy::NetworkAuthentication {
   617        metadata: k8s::ObjectMeta {
   618            namespace: Some(ns.to_string()),
   619            name: Some("allow-pod".to_string()),
   620            ..Default::default()
   621        },
   622        spec: k8s::policy::NetworkAuthenticationSpec {
   623            networks: ips
   624                .into_iter()
   625                .map(|ip| k8s::policy::Network {
   626                    cidr: ip.into(),
   627                    except: None,
   628                })
   629                .collect(),
   630        },
   631    }
   632}
   633
   634fn http_route(name: &str, ns: &str, server_name: &str, path: &str) -> k8s::policy::HttpRoute {
   635    k8s::policy::HttpRoute {
   636        metadata: k8s::ObjectMeta {
   637            namespace: Some(ns.to_string()),
   638            name: Some(name.to_string()),
   639            ..Default::default()
   640        },
   641        spec: k8s::policy::HttpRouteSpec {
   642            inner: k8s::policy::httproute::CommonRouteSpec {
   643                parent_refs: Some(vec![k8s_gateway_api::ParentReference {
   644                    group: Some("policy.linkerd.io".to_string()),
   645                    kind: Some("Server".to_string()),
   646                    namespace: Some(ns.to_string()),
   647                    name: server_name.to_string(),
   648                    section_name: None,
   649                    port: None,
   650                }]),
   651            },
   652            hostnames: None,
   653            rules: Some(vec![k8s::policy::httproute::HttpRouteRule {
   654                matches: Some(vec![k8s::policy::httproute::HttpRouteMatch {
   655                    path: Some(k8s::policy::httproute::HttpPathMatch::Exact {
   656                        value: path.to_string(),
   657                    }),
   658                    ..Default::default()
   659                }]),
   660                filters: None,
   661                backend_refs: None,
   662                timeouts: None,
   663            }]),
   664        },
   665        status: None,
   666    }
   667}

View as plain text