Skip to content

Commit 9c5f29f

Browse files
authored
Merge pull request #5 from ynqa/bronze
Inclusterconfig, Bearer token, and Basic auth
2 parents fe27390 + dc215b7 commit 9c5f29f

File tree

10 files changed

+262
-30
lines changed

10 files changed

+262
-30
lines changed

.travis.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
language: rust
2+
3+
rust:
4+
- stable
5+
- beta
6+
- nightly
7+
8+
matrix:
9+
allow_failures:
10+
- rust: nightly
11+
12+
script:
13+
- cargo build
14+
- cargo test -v

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ serde_derive = "1.0.79"
1919
serde_yaml = "0.8.5"
2020
openssl = "0.10.12"
2121
k8s-openapi = { git = "https://github.com/Arnavion/k8s-openapi-codegen", branch = "master", features = ["v1_10"] }
22+
23+
[dev-dependencies]
24+
tempfile = "3.0.4"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# kubernetes-rust
22

3-
[![Client Support Level](https://img.shields.io/badge/kubernetes%20client-alpha-green.svg?style=flat&colorA=306CE8)](http://bit.ly/kubernetes-client-support-badge)
3+
[![Build Status](https://travis-ci.com/ynqa/kubernetes-rust.svg?branch=master)](https://travis-ci.com/ynqa/kubernetes-rust)
4+
[![Client Capabilities](https://img.shields.io/badge/Kubernetes%20client-Bronze-blue.svg?style=plastic&colorB=cd7f32&colorA=306CE8)](http://bit.ly/kubernetes-client-capabilities-badge)
5+
[![Client Support Level](https://img.shields.io/badge/kubernetes%20client-beta-green.svg?style=plastic&colorA=306CE8)](http://bit.ly/kubernetes-client-support-badge)
46

57
Rust client for [Kubernetes](http://kubernetes.io) API.
68

examples/incluster_config.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
extern crate failure;
2+
extern crate k8s_openapi;
3+
extern crate kubernetes;
4+
5+
use k8s_openapi::v1_10::api::core::v1;
6+
use kubernetes::client::APIClient;
7+
use kubernetes::config;
8+
9+
fn main() {
10+
let kubeconfig = config::incluster_config().expect("failed to load incluster config");
11+
let kubeclient = APIClient::new(kubeconfig);
12+
let req = v1::Pod::list_core_v1_namespaced_pod(
13+
"kube-system",
14+
None,
15+
None,
16+
None,
17+
None,
18+
None,
19+
None,
20+
None,
21+
None,
22+
None,
23+
).expect("failed to define list pod");
24+
let list_pod = kubeclient
25+
.request::<v1::PodList>(req)
26+
.expect("failed to list up pods");
27+
println!("{:?}", list_pod);
28+
}

src/client/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde::de::DeserializeOwned;
66

77
use super::config::Configuration;
88

9+
/// APIClient requires `config::Configuration` includes client to connect with kubernetes cluster.
910
pub struct APIClient {
1011
configuration: Rc<Configuration>,
1112
}
@@ -16,6 +17,7 @@ impl APIClient {
1617
APIClient { configuration: rc }
1718
}
1819

20+
/// Returns kubernetes resources binded `Arnavion/k8s-openapi-codegen` APIs.
1921
pub fn request<T>(&self, request: http::Request<Vec<u8>>) -> Result<T, Error>
2022
where
2123
T: DeserializeOwned,
@@ -28,7 +30,7 @@ impl APIClient {
2830
http::Method::DELETE => self.configuration.client.delete(&uri_str),
2931
http::Method::PUT => self.configuration.client.put(&uri_str),
3032
other => {
31-
return Err(Error::from(format_err!("invalid method: {}", other)));
33+
return Err(Error::from(format_err!("Invalid method: {}", other)));
3234
}
3335
}.body(body);
3436

src/config/apis.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde_yaml;
66

77
use config::utils;
88

9+
/// Config stores information to connect remote kubernetes cluster.
910
#[derive(Clone, Debug, Serialize, Deserialize)]
1011
pub struct Config {
1112
pub kind: Option<String>,
@@ -21,24 +22,28 @@ pub struct Config {
2122
pub extensions: Option<Vec<NamedExtension>>,
2223
}
2324

25+
/// Preferences stores extensions for cli.
2426
#[derive(Clone, Debug, Serialize, Deserialize)]
2527
pub struct Preferences {
2628
pub colors: Option<bool>,
2729
pub extensions: Option<Vec<NamedExtension>>,
2830
}
2931

32+
/// NamedExtention associates name with extension.
3033
#[derive(Clone, Debug, Serialize, Deserialize)]
3134
pub struct NamedExtension {
3235
pub name: String,
3336
pub extension: String,
3437
}
3538

39+
/// NamedCluster associates name with cluster.
3640
#[derive(Clone, Debug, Serialize, Deserialize)]
3741
pub struct NamedCluster {
3842
pub name: String,
3943
pub cluster: Cluster,
4044
}
4145

46+
/// Cluster stores information to connect kubernetes cluster.
4247
#[derive(Clone, Debug, Serialize, Deserialize)]
4348
pub struct Cluster {
4449
pub server: String,
@@ -50,13 +55,15 @@ pub struct Cluster {
5055
pub certificate_authority_data: Option<String>,
5156
}
5257

58+
/// NamedAuthInfo associates name with authentication.
5359
#[derive(Clone, Debug, Serialize, Deserialize)]
5460
pub struct NamedAuthInfo {
5561
pub name: String,
5662
#[serde(rename = "user")]
5763
pub auth_info: AuthInfo,
5864
}
5965

66+
/// AuthInfo stores information to tell cluster who you are.
6067
#[derive(Clone, Debug, Serialize, Deserialize)]
6168
pub struct AuthInfo {
6269
pub username: Option<String>,
@@ -82,12 +89,14 @@ pub struct AuthInfo {
8289
pub impersonate_groups: Option<Vec<String>>,
8390
}
8491

92+
/// NamedContext associates name with context.
8593
#[derive(Clone, Debug, Serialize, Deserialize)]
8694
pub struct NamedContext {
8795
pub name: String,
8896
pub context: Context,
8997
}
9098

99+
/// Context stores tuple of cluster and user information.
91100
#[derive(Clone, Debug, Serialize, Deserialize)]
92101
pub struct Context {
93102
pub cluster: String,
@@ -105,20 +114,20 @@ impl Config {
105114
}
106115

107116
impl Cluster {
108-
pub fn load_certificate_authority(&self) -> Result<Option<Vec<u8>>, Error> {
109-
utils::data_or_file(
117+
pub fn load_certificate_authority(&self) -> Result<Vec<u8>, Error> {
118+
utils::data_or_file_with_base64(
110119
&self.certificate_authority_data,
111120
&self.certificate_authority,
112121
)
113122
}
114123
}
115124

116125
impl AuthInfo {
117-
pub fn load_client_certificate(&self) -> Result<Option<Vec<u8>>, Error> {
118-
utils::data_or_file(&self.client_certificate_data, &self.client_certificate)
126+
pub fn load_client_certificate(&self) -> Result<Vec<u8>, Error> {
127+
utils::data_or_file_with_base64(&self.client_certificate_data, &self.client_certificate)
119128
}
120129

121-
pub fn load_client_key(&self) -> Result<Option<Vec<u8>>, Error> {
122-
utils::data_or_file(&self.client_key_data, &self.client_key)
130+
pub fn load_client_key(&self) -> Result<Vec<u8>, Error> {
131+
utils::data_or_file_with_base64(&self.client_key_data, &self.client_key)
123132
}
124133
}

src/config/incluster_config.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::env;
2+
3+
use failure::Error;
4+
use openssl::x509::X509;
5+
6+
use config::utils;
7+
8+
pub const SERVICE_HOSTENV: &str = "KUBERNETES_SERVICE_HOST";
9+
pub const SERVICE_PORTENV: &str = "KUBERNETES_SERVICE_PORT";
10+
const SERVICE_TOKENFILE: &str = "/var/run/secrets/kubernetes.io/serviceaccount/token";
11+
const SERVICE_CERTFILE: &str = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
12+
13+
/// Returns kubernetes address from specified environment variables.
14+
pub fn kube_server() -> Option<String> {
15+
let f = |(h, p)| format!("https://{}:{}", h, p);
16+
kube_host().and_then(|h| kube_port().map(|p| f((h, p))))
17+
}
18+
19+
fn kube_host() -> Option<String> {
20+
env::var(SERVICE_HOSTENV).ok()
21+
}
22+
23+
fn kube_port() -> Option<String> {
24+
env::var(SERVICE_PORTENV).ok()
25+
}
26+
27+
/// Returns token from specified path in cluster.
28+
pub fn load_token() -> Result<String, Error> {
29+
utils::data_or_file(&None, &Some(SERVICE_TOKENFILE.to_string()))
30+
}
31+
32+
/// Returns certification from specified path in cluster.
33+
pub fn load_cert() -> Result<X509, Error> {
34+
let ca = utils::data_or_file_with_base64(&None, &Some(SERVICE_CERTFILE.to_string()))?;
35+
X509::from_pem(&ca).map_err(Error::from)
36+
}
37+
38+
#[test]
39+
fn test_kube_host() {
40+
let expected = "fake.io";
41+
env::set_var(SERVICE_HOSTENV, expected);
42+
assert_eq!(kube_host().unwrap(), expected);
43+
}
44+
45+
#[test]
46+
fn test_kube_port() {
47+
let expected = "8080";
48+
env::set_var(SERVICE_PORTENV, expected);
49+
assert_eq!(kube_port().unwrap(), expected);
50+
}
51+
52+
#[test]
53+
fn test_kube_server() {
54+
let host = "fake.io";
55+
let port = "8080";
56+
env::set_var(SERVICE_HOSTENV, host);
57+
env::set_var(SERVICE_PORTENV, port);
58+
assert_eq!(kube_server().unwrap(), "https://fake.io:8080");
59+
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use openssl::x509::X509;
77

88
use config::apis::{AuthInfo, Cluster, Config, Context};
99

10+
/// KubeConfigLoader loads current context, cluster, and authentication information.
1011
#[derive(Debug)]
1112
pub struct KubeConfigLoader {
1213
pub current_context: Context,
@@ -43,8 +44,8 @@ impl KubeConfigLoader {
4344
}
4445

4546
pub fn p12(&self, password: &str) -> Result<Pkcs12, Error> {
46-
let client_cert = self.user.load_client_certificate()?.unwrap();
47-
let client_key = self.user.load_client_key()?.unwrap();
47+
let client_cert = &self.user.load_client_certificate()?;
48+
let client_key = &self.user.load_client_key()?;
4849

4950
let x509 = X509::from_pem(&client_cert)?;
5051
let pkey = PKey::private_key_from_pem(&client_key)?;
@@ -55,7 +56,7 @@ impl KubeConfigLoader {
5556
}
5657

5758
pub fn ca(&self) -> Result<X509, Error> {
58-
let ca = self.cluster.load_certificate_authority()?.unwrap();
59+
let ca = &self.cluster.load_certificate_authority()?;
5960
X509::from_pem(&ca).map_err(Error::from)
6061
}
6162
}

src/config/mod.rs

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
mod apis;
2-
mod loader;
2+
mod incluster_config;
3+
mod kube_config;
34
mod utils;
45

6+
use base64;
57
use failure::Error;
68
use reqwest::{header, Certificate, Client, Identity};
79

8-
use self::loader::KubeConfigLoader;
10+
use self::kube_config::KubeConfigLoader;
911

12+
/// Configuration stores kubernetes path and client for requests.
1013
pub struct Configuration {
1114
pub base_path: String,
1215
pub client: Client,
@@ -21,29 +24,91 @@ impl Configuration {
2124
}
2225
}
2326

27+
/// Returns a config includes authentication and cluster infomation from kubeconfig file.
28+
///
29+
/// # Example
30+
/// ```no_run
31+
/// use kubernetes::config;
32+
///
33+
/// let kubeconfig = config::load_kube_config()
34+
/// .expect("failed to load kubeconfig");
35+
/// ```
2436
pub fn load_kube_config() -> Result<Configuration, Error> {
25-
let kubeconfig = utils::kube_path()
37+
let kubeconfig = utils::kubeconfig_path()
2638
.or_else(utils::default_kube_path)
27-
.ok_or(format_err!("Unable to load config"))?;
39+
.ok_or(format_err!("Unable to load kubeconfig"))?;
2840

2941
let loader = KubeConfigLoader::load(kubeconfig)?;
3042

31-
let password = " ";
32-
let p12 = loader.p12(password)?;
33-
let req_p12 = Identity::from_pkcs12_der(&p12.to_der()?, password)?;
43+
let p12 = loader.p12(" ")?;
44+
let req_p12 = Identity::from_pkcs12_der(&p12.to_der()?, " ")?;
3445

3546
let ca = loader.ca()?;
3647
let req_ca = Certificate::from_der(&ca.to_der()?)?;
3748

49+
let mut headers = header::HeaderMap::new();
50+
51+
match (
52+
utils::data_or_file(&loader.user.token, &loader.user.token_file),
53+
(loader.user.username, loader.user.password),
54+
) {
55+
(Ok(token), _) => {
56+
headers.insert(
57+
header::AUTHORIZATION,
58+
header::HeaderValue::from_str(&format!("Bearer {}", token))?,
59+
);
60+
}
61+
(_, (Some(u), Some(p))) => {
62+
let encoded = base64::encode(&format!("{}:{}", u, p));
63+
headers.insert(
64+
header::AUTHORIZATION,
65+
header::HeaderValue::from_str(&format!("Basic {}", encoded))?,
66+
);
67+
}
68+
_ => {}
69+
}
70+
3871
let client_builder = Client::builder()
3972
.identity(req_p12)
40-
.add_root_certificate(req_ca);
41-
42-
let mut headers = header::HeaderMap::new();
43-
headers.insert(header::AUTHORIZATION, header::HeaderValue::from_static(""));
73+
.add_root_certificate(req_ca)
74+
.default_headers(headers);
4475

4576
Ok(Configuration::new(
4677
loader.cluster.server,
4778
client_builder.build()?,
4879
))
4980
}
81+
82+
/// Returns a config which is used by clients within pods on kubernetes.
83+
/// It will return an error if called from out of kubernetes cluster.
84+
///
85+
/// # Example
86+
/// ```no_run
87+
/// use kubernetes::config;
88+
///
89+
/// let kubeconfig = config::incluster_config()
90+
/// .expect("failed to load incluster config");
91+
/// ```
92+
pub fn incluster_config() -> Result<Configuration, Error> {
93+
let server = incluster_config::kube_server().ok_or(format_err!(
94+
"Unable to load incluster config, {} and {} must be defined",
95+
incluster_config::SERVICE_HOSTENV,
96+
incluster_config::SERVICE_PORTENV
97+
))?;
98+
99+
let ca = incluster_config::load_cert()?;
100+
let req_ca = Certificate::from_der(&ca.to_der()?)?;
101+
102+
let token = incluster_config::load_token()?;
103+
let mut headers = header::HeaderMap::new();
104+
headers.insert(
105+
header::AUTHORIZATION,
106+
header::HeaderValue::from_str(&format!("Bearer {}", token))?,
107+
);
108+
109+
let client_builder = Client::builder()
110+
.add_root_certificate(req_ca)
111+
.default_headers(headers);
112+
113+
Ok(Configuration::new(server, client_builder.build()?))
114+
}

0 commit comments

Comments
 (0)