Skip to content

Commit 3013b2a

Browse files
authored
Merge pull request #75 from renoki-co/feature/in-cluster-auth
[2.x] In-cluster configuration for Pod-based apps
2 parents 1545ca3 + 29c00eb commit 3013b2a

File tree

5 files changed

+108
-19
lines changed

5 files changed

+108
-19
lines changed

.github/workflows/ci.yml

+8
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ jobs:
7171
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
7272
composer update --${{ matrix.prefer }} --prefer-dist --no-interaction --no-suggest
7373
74+
- name: Setup in-cluster config
75+
run: |
76+
sudo mkdir -p /var/run/secrets/kubernetes.io/serviceaccount
77+
echo "some-token" | sudo tee /var/run/secrets/kubernetes.io/serviceaccount/token
78+
echo "c29tZS1jZXJ0Cg==" | sudo tee /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
79+
echo "some-namespace" | sudo tee /var/run/secrets/kubernetes.io/serviceaccount/namespace
80+
sudo chmod -R 777 /var/run/secrets/kubernetes.io/serviceaccount/
81+
7482
- name: Run tests
7583
run: |
7684
vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml

docs/Cluster.md

+18
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,21 @@ For testing purposes or local checkups, you can disable SSL checks:
5151
```php
5252
$cluster->withoutSslChecks();
5353
```
54+
55+
## In-Cluster Configuration
56+
57+
Kubernetes allows Pods to access [the internal kubeapi within a container](https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/).
58+
59+
PhpK8s allows you to set up an in-cluster-ready client with minimal configuration. Please keep in mind that this works only within pods that run in a Kubernetes cluster.
60+
61+
```php
62+
use RenokiCo\PhpK8s\KubernetesCluster;
63+
64+
$cluster = new KubernetesCluster('https://kubernetes.default.svc');
65+
66+
$cluster->inClusterConfiguration();
67+
68+
foreach ($cluster->getAllServices() as $svc) {
69+
//
70+
}
71+
```

src/KubernetesCluster.php

-7
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,6 @@ class KubernetesCluster
1919
*/
2020
protected $url;
2121

22-
/**
23-
* The API port.
24-
*
25-
* @var int
26-
*/
27-
protected $port = 8080;
28-
2922
/**
3023
* The class name for the K8s resource.
3124
*

src/Traits/Cluster/AuthenticatesCluster.php

+38-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace RenokiCo\PhpK8s\Traits\Cluster;
44

5+
use RenokiCo\PhpK8s\Kinds\K8sResource;
6+
57
trait AuthenticatesCluster
68
{
79
/**
@@ -52,7 +54,7 @@ trait AuthenticatesCluster
5254
*/
5355
public function withToken(string $token = null)
5456
{
55-
$this->token = str_replace(["\r", "\n"], '', $token);
57+
$this->token = $this->normalize($token);
5658

5759
return $this;
5860
}
@@ -134,4 +136,39 @@ public function withoutSslChecks()
134136

135137
return $this;
136138
}
139+
140+
/**
141+
* Load the in-cluster configuration to run the code
142+
* under a Pod in a cluster.
143+
*
144+
* @return $this
145+
*/
146+
public function inClusterConfiguration()
147+
{
148+
if (file_exists($tokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token')) {
149+
$this->loadTokenFromFile($tokenPath);
150+
}
151+
152+
if (file_exists($caPath = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt')) {
153+
$this->withCaCertificate($caPath);
154+
}
155+
156+
if ($namespace = @file_get_contents('/var/run/secrets/kubernetes.io/serviceaccount/namespace')) {
157+
K8sResource::setDefaultNamespace($this->normalize($namespace));
158+
}
159+
160+
return $this;
161+
}
162+
163+
/**
164+
* Replace \r and \n with nothing. Used to read
165+
* strings from files that might contain extra chars.
166+
*
167+
* @param string $content
168+
* @return string
169+
*/
170+
protected function normalize(string $content): string
171+
{
172+
return str_replace(["\r", "\n"], '', $content);
173+
}
137174
}

tests/KubeConfigTest.php

+44-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use RenokiCo\PhpK8s\Exceptions\KubeConfigClusterNotFound;
66
use RenokiCo\PhpK8s\Exceptions\KubeConfigContextNotFound;
77
use RenokiCo\PhpK8s\Exceptions\KubeConfigUserNotFound;
8+
use RenokiCo\PhpK8s\Kinds\K8sResource;
89
use RenokiCo\PhpK8s\KubernetesCluster;
910

1011
class KubeConfigTest extends TestCase
@@ -21,13 +22,15 @@ public function setUp(): void
2122

2223
public function test_kube_config_from_yaml_file_with_base64_encoded_ssl()
2324
{
24-
$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube');
25+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
26+
27+
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube');
2528

2629
[
2730
'verify' => $caPath,
2831
'cert' => $certPath,
2932
'ssl_key' => $keyPath,
30-
] = $this->cluster->getClient()->getConfig();
33+
] = $cluster->getClient()->getConfig();
3134

3235
$this->assertEquals("some-ca\n", file_get_contents($caPath));
3336
$this->assertEquals("some-cert\n", file_get_contents($certPath));
@@ -36,13 +39,15 @@ public function test_kube_config_from_yaml_file_with_base64_encoded_ssl()
3639

3740
public function test_kube_config_from_yaml_file_with_paths_to_ssl()
3841
{
39-
$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-2');
42+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
43+
44+
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-2');
4045

4146
[
4247
'verify' => $caPath,
4348
'cert' => $certPath,
4449
'ssl_key' => $keyPath,
45-
] = $this->cluster->getClient()->getConfig();
50+
] = $cluster->getClient()->getConfig();
4651

4752
$this->assertEquals('/path/to/.minikube/ca.crt', $caPath);
4853
$this->assertEquals('/path/to/.minikube/client.crt', $certPath);
@@ -51,40 +56,68 @@ public function test_kube_config_from_yaml_file_with_paths_to_ssl()
5156

5257
public function test_kube_config_from_yaml_cannot_load_if_no_cluster()
5358
{
59+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
60+
5461
$this->expectException(KubeConfigClusterNotFound::class);
5562

56-
$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-cluster');
63+
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-cluster');
5764
}
5865

5966
public function test_kube_config_from_yaml_cannot_load_if_no_user()
6067
{
68+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
69+
6170
$this->expectException(KubeConfigUserNotFound::class);
6271

63-
$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-user');
72+
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'minikube-without-user');
6473
}
6574

6675
public function test_kube_config_from_yaml_cannot_load_if_wrong_context()
6776
{
77+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
78+
6879
$this->expectException(KubeConfigContextNotFound::class);
6980

70-
$this->cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'inexistent-context');
81+
$cluster->fromKubeConfigYamlFile(__DIR__.'/cluster/kubeconfig.yaml', 'inexistent-context');
7182
}
7283

7384
public function test_http_authentication()
7485
{
75-
$this->cluster->httpAuthentication('some-user', 'some-password');
86+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
7687

77-
['auth' => $auth] = $this->cluster->getClient()->getConfig();
88+
$cluster->httpAuthentication('some-user', 'some-password');
89+
90+
['auth' => $auth] = $cluster->getClient()->getConfig();
7891

7992
$this->assertEquals(['some-user', 'some-password'], $auth);
8093
}
8194

8295
public function test_bearer_token_authentication()
8396
{
84-
$this->cluster->loadTokenFromFile(__DIR__.'/cluster/token.txt');
97+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
98+
99+
$cluster->loadTokenFromFile(__DIR__.'/cluster/token.txt');
85100

86-
['headers' => ['authorization' => $token]] = $this->cluster->getClient()->getConfig();
101+
['headers' => ['authorization' => $token]] = $cluster->getClient()->getConfig();
87102

88103
$this->assertEquals('Bearer some-token', $token);
89104
}
105+
106+
public function test_in_cluster_config()
107+
{
108+
$cluster = new KubernetesCluster('http://127.0.0.1:8080');
109+
110+
$cluster->inClusterConfiguration();
111+
112+
[
113+
'headers' => ['authorization' => $token],
114+
'verify' => $caPath,
115+
] = $cluster->getClient()->getConfig();
116+
117+
$this->assertEquals('Bearer some-token', $token);
118+
$this->assertEquals('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt', $caPath);
119+
$this->assertEquals('some-namespace', K8sResource::$defaultNamespace);
120+
121+
K8sResource::setDefaultNamespace('default');
122+
}
90123
}

0 commit comments

Comments
 (0)