-
Notifications
You must be signed in to change notification settings - Fork 104
/
mod.rs
314 lines (258 loc) · 10.1 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! azure metadata fetcher
mod crypto;
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use openssh_keys::PublicKey;
use update_ssh_keys::AuthorizedKeyEntry;
use self::crypto::x509;
use errors::*;
use network;
use providers::MetadataProvider;
use retry;
use util;
header! {(MSAgentName, "x-ms-agent-name") => [String]}
header! {(MSVersion, "x-ms-version") => [String]}
header! {(MSCipherName, "x-ms-cipher-name") => [String]}
header! {(MSCert, "x-ms-guest-agent-public-x509-cert") => [String]}
const OPTION_245: &str = "OPTION_245";
const MS_AGENT_NAME: &str = "com.coreos.metadata";
const MS_VERSION: &str = "2012-11-30";
const SMIME_HEADER: &str = "\
MIME-Version:1.0
Content-Disposition: attachment; filename=/home/core/encrypted-ssh-cert.pem
Content-Type: application/x-pkcs7-mime; name=/home/core/encrypted-ssh-cert.pem
Content-Transfer-Encoding: base64
";
#[derive(Debug, Deserialize, Clone, Default)]
struct GoalState {
#[serde(rename = "Container")]
pub container: Container
}
#[derive(Debug, Deserialize, Clone, Default)]
struct Container {
#[serde(rename = "RoleInstanceList")]
pub role_instance_list: RoleInstanceList
}
#[derive(Debug, Deserialize, Clone, Default)]
struct RoleInstanceList {
#[serde(rename = "RoleInstance", default)]
pub role_instances: Vec<RoleInstance>
}
#[derive(Debug, Deserialize, Clone)]
struct RoleInstance {
#[serde(rename = "Configuration")]
pub configuration: Configuration
}
#[derive(Debug, Deserialize, Clone)]
struct Configuration {
#[serde(rename = "Certificates", default)]
pub certificates: String,
#[serde(rename = "SharedConfig", default)]
pub shared_config: String,
}
#[derive(Debug, Deserialize, Clone)]
struct CertificatesFile {
#[serde(rename = "Data", default)]
pub data: String
}
#[derive(Debug, Deserialize, Clone)]
struct Versions {
#[serde(rename = "Supported")]
pub supported: Supported
}
#[derive(Debug, Deserialize, Clone)]
struct Supported {
#[serde(rename = "Version", default)]
pub versions: Vec<String>
}
#[derive(Debug, Deserialize, Clone)]
struct SharedConfig {
#[serde(rename = "Incarnation")]
pub incarnation: Incarnation,
#[serde(rename = "Instances")]
pub instances: Instances,
}
#[derive(Debug, Deserialize, Clone)]
struct Incarnation {
pub instance: String,
}
#[derive(Debug, Deserialize, Clone)]
struct Instances {
#[serde(rename = "Instance", default)]
pub instances: Vec<Instance>,
}
#[derive(Debug, Deserialize, Clone)]
struct Instance {
pub id: String,
pub address: String,
#[serde(rename = "InputEndpoints")]
pub input_endpoints: InputEndpoints,
}
#[derive(Debug, Deserialize, Clone)]
struct InputEndpoints {
#[serde(rename = "Endpoint", default)]
pub endpoints: Vec<Endpoint>,
}
#[derive(Debug, Deserialize, Clone)]
struct Endpoint {
#[serde(rename = "loadBalancedPublicAddress", default)]
pub load_balanced_public_address: String,
}
#[derive(Debug, Copy, Clone, Default)]
struct Attributes {
pub virtual_ipv4: Option<IpAddr>,
pub dynamic_ipv4: Option<IpAddr>,
}
#[derive(Debug, Clone)]
pub struct Azure {
client: retry::Client,
endpoint: IpAddr,
goal_state: GoalState,
}
impl Azure {
pub fn new() -> Result<Azure> {
let addr = Azure::get_fabric_address()
.chain_err(|| "failed to get fabric address")?;
let client = retry::Client::new()?
.header(MSAgentName(MS_AGENT_NAME.to_owned()))
.header(MSVersion(MS_VERSION.to_owned()));
let mut azure = Azure {
client,
endpoint: addr,
goal_state: GoalState::default(),
};
// make sure the metadata service is compatible with our version
azure.is_fabric_compatible(MS_VERSION)
.chain_err(|| "failed version compatibility check")?;
// populate goalstate
azure.goal_state = azure.get_goal_state()?;
Ok(azure)
}
fn get_goal_state(&self) -> Result<GoalState> {
self.client.get(retry::Xml, format!("http://{}/machine/?comp=goalstate", self.endpoint)).send()
.chain_err(|| "failed to get goal state")?
.ok_or_else(|| "failed to get goal state: not found response".into())
}
fn get_fabric_address() -> Result<IpAddr> {
let v = util::dns_lease_key_lookup(OPTION_245)?;
// value is an 8 digit hex value. convert it to u32 and
// then parse that into an ip. Ipv4Addr::from(u32)
// performs conversion from big-endian
trace!("found fabric address in hex - {:?}", v);
let dec = u32::from_str_radix(&v, 16)
.chain_err(|| format!("failed to convert '{}' from hex", v))?;
Ok(IpAddr::V4(dec.into()))
}
fn is_fabric_compatible(&self, version: &str) -> Result<()> {
let versions: Versions = self.client.get(retry::Xml, format!("http://{}/?comp=versions", self.endpoint)).send()
.chain_err(|| "failed to get versions")?
.ok_or_else(|| "failed to get versions: not found")?;
if versions.supported.versions.iter().any(|v| v == version) {
Ok(())
} else {
Err(format!("fabric version {} not compatible with fabric address {}", MS_VERSION, self.endpoint).into())
}
}
fn get_certs_endpoint(&self) -> Result<String> {
// grab the certificates endpoint from the xml and return it
let cert_endpoint: &str = &self.goal_state.container.role_instance_list.role_instances[0].configuration.certificates;
Ok(String::from(cert_endpoint))
}
fn get_certs(&self, mangled_pem: String) -> Result<String> {
// get the certificates
let endpoint = self.get_certs_endpoint()
.chain_err(|| "failed to get certs endpoint")?;
let certs: CertificatesFile = self.client.get(retry::Xml, endpoint)
.header(MSCipherName("DES_EDE3_CBC".to_owned()))
.header(MSCert(mangled_pem))
.send()
.chain_err(|| "failed to get certificates")?
.ok_or_else(|| "failed to get certificates: not found")?;
// the cms decryption expects it to have MIME information on the top
// since cms is really for email attachments...don't tell the cops.
let mut smime = String::from(SMIME_HEADER);
smime.push_str(&certs.data);
Ok(smime)
}
// put it all together
fn get_ssh_pubkey(&self) -> Result<PublicKey> {
// first we have to get the certificates endoint.
// we have to generate the rsa public/private keypair and the x509 cert
// that we use to make the request. this is equivalent to
// `openssl req -x509 -nodes -subj /CN=LinuxTransport -days 365 -newkey rsa:2048 -keyout private.pem -out cert.pem`
let (x509, pkey) = x509::generate_cert(&x509::Config::new(2048, 365))
.chain_err(|| "failed to generate keys")?;
// mangle the pem file for the request
let mangled_pem = crypto::mangle_pem(&x509)
.chain_err(|| "failed to mangle pem")?;
// fetch the encrypted cms blob from the certs endpoint
let smime = self.get_certs(mangled_pem)
.chain_err(|| "failed to get certs")?;
// decrypt the cms blob
let p12 = crypto::decrypt_cms(smime.as_bytes(), &pkey, &x509)
.chain_err(|| "failed to decrypt cms blob")?;
// convert that to the OpenSSH public key format
let ssh_pubkey = crypto::p12_to_ssh_pubkey(&p12)
.chain_err(|| "failed to convert pkcs12 blob to ssh pubkey")?;
Ok(ssh_pubkey)
}
fn get_attributes(&self) -> Result<Attributes> {
let endpoint = &self.goal_state.container.role_instance_list.role_instances[0].configuration.shared_config;
let shared_config: SharedConfig = self.client.get(retry::Xml, endpoint.to_string()).send()
.chain_err(|| "failed to get shared configuration")?
.ok_or_else(|| "failed to get shared configuration: not found")?;
let mut attributes = Attributes::default();
for instance in shared_config.instances.instances {
if instance.id == shared_config.incarnation.instance {
attributes.dynamic_ipv4 = Some(instance.address.parse()
.chain_err(|| format!("failed to parse instance ip address: {}", instance.address))?);
for endpoint in instance.input_endpoints.endpoints {
attributes.virtual_ipv4 = match endpoint.load_balanced_public_address.parse::<SocketAddr>() {
Ok(lbpa) => Some(lbpa.ip()),
Err(_) => continue,
};
}
}
}
Ok(attributes)
}
}
impl MetadataProvider for Azure {
fn attributes(&self) -> Result<HashMap<String, String>> {
let attributes = self.get_attributes()?;
let mut out = HashMap::with_capacity(2);
if let Some(virtual_ipv4) = attributes.virtual_ipv4 {
out.insert("AZURE_IPV4_VIRTUAL".to_string(), virtual_ipv4.to_string());
}
if let Some(dynamic_ipv4) = attributes.dynamic_ipv4 {
out.insert("AZURE_IPV4_DYNAMIC".to_string(), dynamic_ipv4.to_string());
}
Ok(out)
}
fn hostname(&self) -> Result<Option<String>> {
Ok(None)
}
fn ssh_keys(&self) -> Result<Vec<AuthorizedKeyEntry>> {
let key = self.get_ssh_pubkey()?;
Ok(vec![AuthorizedKeyEntry::Valid{key}])
}
fn networks(&self) -> Result<Vec<network::Interface>> {
Ok(vec![])
}
fn network_devices(&self) -> Result<Vec<network::Device>> {
Ok(vec![])
}
}