-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.xml
245 lines (172 loc) · 14.1 KB
/
index.xml
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
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Jonatan Reiners - Thoughts on Jonatan Reiners - Thoughts</title>
<link>http://www.encc.de/index.xml</link>
<description>Recent content in Jonatan Reiners - Thoughts on Jonatan Reiners - Thoughts</description>
<generator>Hugo -- gohugo.io</generator>
<language>en-us</language>
<copyright>&copy; 2017 Jonatan Reiners</copyright>
<lastBuildDate>Wed, 20 Apr 2016 00:00:00 +0000</lastBuildDate>
<atom:link href="/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Using Azure KeyVault as property source in Spring Boot</title>
<link>http://www.encc.de/post/azure_keyvault_with_spring/</link>
<pubDate>Sun, 02 Apr 2017 17:04:45 +0200</pubDate>
<guid>http://www.encc.de/post/azure_keyvault_with_spring/</guid>
<description>
<p>In my current project we had first contact with the Azure Cloud. Azure offers the <a href="https://azure.microsoft.com/en-us/services/key-vault/" target="_blank">KeyVault</a> as their product to store secrets safely in the environment you deploy your services to. While it supports different scenarios, I was focussing my efforts on storing secrets and use it as a source for properties in my Spring Boot application.</p>
<p>When we are using the KeyVault we can store a minimum of secrets to access it in the immediate environment of the application. We can then retrieve all other required secrets from the KeyVault, where we have more control and a central location for our secrets.</p>
<p>First I will start with the implementation. Later I want to mention some considerations if you want to implement it too.</p>
<h2 id="implementation">Implementation</h2>
<h3 id="connecting-to-the-keyvault">Connecting to the KeyVault</h3>
<p>Since we are using Kotlin, we have to rely on the <a href="https://github.com/Azure/azure-sdk-for-java" target="_blank">Java SDK for azure</a>. I couldn&rsquo;t find any good documentation that explains the authorization for the KeyVault part of the SDK. The reason is, that the SDK is developed by a very small team and just reached 1.0.0: a lot of documentation is outdated.</p>
<p>The main class to use is the <code>com.microsoft.azure.keyvault.KeyVaultClient</code>. The constructor takes <code>com.microsoft.rest.credentials.ServiceClientCredentials</code> but this is a trap and an example of inheritance gone wrong. In the whole SDK you will find several implementations of that interface but they will not necessarily work with the <code>KeyVaultClient</code>. The implementor of the SDK did a good job of documenting the classes in the KeyVault package therefore a look at the <a href="http://azure.github.io/azure-sdk-for-java/com/microsoft/azure/keyvault/authentication/KeyVaultCredentials.html" target="_blank">KeyVaultCredentials</a> will reveal the solution. I did nothing else but implementing/copying this straight from the documentation.</p>
<pre><code>class KeyVaultCredentialsImpl : KeyVaultCredentials() {
override fun doAuthenticate(authorization: String, resource: String, s: String?): String {
val token = getAccessTokenFromClientCredentials(authorization, resource)
return token.accessToken
}
private fun getAccessTokenFromClientCredentials(authorization: String, resource: String): AuthenticationResult {
val context: AuthenticationContext
val result: AuthenticationResult?
var service: ExecutorService? = null
val c = KeyVaultConfiguration
try {
service = Executors.newFixedThreadPool(1)
context = AuthenticationContext(authorization, false, service!!)
val credentials = ClientCredential(c.clientId, c.clientKey)
val future = context.acquireToken(resource, credentials, null)
result = future.get()
} catch (e: Exception) {
throw RuntimeException(e)
} finally {
service!!.shutdown()
}
if (result == null) {
throw RuntimeException(&quot;authentication result was null&quot;)
}
return result
}
}
</code></pre>
<p>This class can be used to create an instance of the <code>KeyVaultClient</code>.</p>
<pre><code>val kvClient = KeyVaultClient(KeyVaultCredentialsImpl())
</code></pre>
<p>The <code>KeyVaultConfiguration</code> is pulling the <em>client id</em> and <em>secret</em> from the environment. This is the recommended way by Microsoft and you can use the <a href="https://github.com/Azure/azure-sdk-for-java/blob/master/AUTH.md#creating-a-service-principal-in-azure" target="_blank">following guide</a> to generate them.</p>
<h4 id="granting-access-to-keyvault">Granting access to KeyVault</h4>
<p>I can recommend the <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" target="_blank">Azure CLI</a> for the job.</p>
<pre><code>az key vault set-policy --name YourKeyVaultName \
--spn na692-your-service-priciple-client-id \
-g ResourceGroup \
--secret-permission get list
</code></pre>
<p>Both operations implemented later require the <em>get</em> and <em>list</em> permissions.</p>
<h3 id="registering-a-property-source-in-spring">Registering a property source in spring</h3>
<p>While the process is actually very simple, I had a hard time finding all the bits and pieces of documentation and fit them together.</p>
<p><strong>Step 1</strong></p>
<p>Create an initializer for Spring to load the property source.</p>
<pre><code>class KeyVaultPropertyInitializer : ApplicationContextInitializer&lt;ConfigurableApplicationContext&gt;, Ordered {
override fun initialize(context: ConfigurableApplicationContext) {
val kvClient = KeyVaultClient(KeyVaultCredentialsImpl())
val env = context.environment
try {
KeyVaultConfiguration.valid() // throws exception
env.propertySources.addFirst(KeyVaultPropertySource(KeyVaultOperations(kvClient)))
} catch (e: IllegalArgumentException) {
println(e.message)
}
}
override fun getOrder(): Int {
return Ordered.HIGHEST_PRECEDENCE + 10
}
}
</code></pre>
<p>You implement the <code>initialize</code> method where you create the <code>KeyVaultClient</code>, then create your <code>KeyVaultPropertySource</code> and register it as the first property source.</p>
<p>Being the first property source has the advantage that you can have development or fake values for your secrets in your config files, but if they are available in the KeyVault they will be overwritten. This is beneficial in the production environment, but can become confusing on your local machine. Microsoft allows you to connect to the KeyVault from any machine if you have the right credentials. So better keep this in mind!</p>
<p>I also want to run this initializer early in the process of starting up, since other libraries might expect secrets during their initialization process. The <code>+10</code> on <code>getOrder()</code> allows to sneak in another initializer to be first, but this should be done consciously.</p>
<p><strong>Step 2</strong></p>
<p>Register the initializer for the Spring startup.</p>
<pre><code>public static void main(String[] args) {
new SpringApplicationBuilder(KcDemoApplication.class)
.initializers(new KeyVaultPropertyInitializer())
.run(args);
}
</code></pre>
<p>Just use the builder, it&rsquo;s straightforward.</p>
<p><strong>Step 3</strong></p>
<p>Creating a property source is very easy. While there is a lot of documentation about different approaches and among the many good ways I found the following being very simple and effective. Implement <code>EnumerablePropertySource</code>.</p>
<pre><code>class KeyVaultPropertySource(private val operations: KeyVaultOperations) : EnumerablePropertySource&lt;KeyVaultOperations&gt;(&quot;kv&quot;, operations) {
override fun getPropertyNames(): Array&lt;String&gt; {
val list = operations.list()
return list
}
override fun getProperty(name: String): Any? {
val property = operations.getProperty(name)
return property
}
}
</code></pre>
<p>You just have to implement these two methods. The function follows the name. It is important that <code>getProperty</code> can return anything and has to return <code>null</code> if the property cannot be resolved. The property source will be queried for every property since it is the first source. Nice behavior for any input is important here!</p>
<p>I was confused by the string and type cast of the enumerable source. The string <code>&quot;kv&quot;</code> is important to identify the property source. You cannot add the same source twice.</p>
<p><strong>Step 4</strong></p>
<p>Implementing the actual operations on the KeyVault is the most interesting part. Luckily it&rsquo;s pretty easy and you have few things to watch out for. Logic will stay simple. But let&rsquo;s dive in, you will see.</p>
<pre><code>fun list(): Array&lt;String&gt; {
if (strings == null) {
strings = try {
val u = kvc.listSecrets(KeyVaultConfiguration.vaultUri)
u.stream()
.map({ it.id() })
.map({ it.removePrefix(&quot;${sanitizeUri(KeyVaultConfiguration.vaultUri)}secrets/&quot;) })
.toArray({Array&lt;String&gt;(it, {i -&gt; &quot;&quot;})})
} catch (e: Exception) {
arrayOf()
}
}
return strings as Array&lt;String&gt;
}
</code></pre>
<p>To create a list of secrets we are pulling all secrets from our KeyVault.</p>
<p>We will save this list in order to not ask the KeyVault too many times. Spring will query our source a lot since it has the highest priority. A little caching is very beneficial here.</p>
<p>We only need the <code>id</code> of the secret for processing. You can do some filtering before if you like.</p>
<p>The <code>id</code> will be the actual name of the secret plus the full URL of the KeyVault. We will clean it. Be careful (case-insensitive) here since the URL will be derived from the name which is case sensitive and you will find different styles throughout the fields of your requests to Azure.</p>
<p>In case of an error I will just save an empty list. This is a fast and easy solution. It will render all queries unsuccessful with very little effort.</p>
<p>The actual collection of the values is lazy compared to fetching the available keys. This is not the optimal solution but sufficient.</p>
<pre><code>fun sanitizeUri(uri: String?): String {
if (uri != null &amp;&amp; uri.matches(&quot;.*/$&quot;.toRegex())) {
return uri
} else {
return &quot;${uri}/&quot;
}
}
</code></pre>
<p>If you haven&rsquo;t seen this little helper before: I want to make sure, that there is always a slash in the end since users might omit it in the configuration.</p>
<pre><code>fun getProperty(path: String): Any? {
var name: String = path.replace(&quot;\\.&quot;.toRegex(), &quot;--&quot;)
if (Arrays.asList(*list()).contains(name)) {
try {
return kvc.getSecret(KeyVaultConfiguration.vaultUri, name).value()
} catch (e: KeyVaultErrorException) {
return null
}
} else {
return null
}
}
</code></pre>
<p>Since KeyVault doesn&rsquo;t support <code>.</code> in the name of a secret we will replace the <code>.</code> with a <code>--</code> in the requested secret. With this replacement we are able to store any property in the KeyVault since we can map the typical property format to KeyVault names.</p>
<p>We will also check the cached list first to avoid unnecessary calls to the API.</p>
<p>If successful we return the value and <code>null</code> in any other case.</p>
<h2 id="thoughts-on-architecture">Thoughts on architecture</h2>
<p>We have built a very simple service. The simplicity makes it already kind of robust but also limits its functionality.</p>
<p>You should consider the following aspects if you want to implement your own solution.</p>
<ol>
<li>The list of values is only fetched in the beginning. While this is good enough for most cases you may want to refresh after a while. Fetching values already supports properties added later because it&rsquo;s lazy.</li>
<li>This implementation fails silently even without log message. You may want to be more verbose or add an option to fail hard. In production you may want your service to fail if you can&rsquo;t fetch the secrets for any reason.</li>
<li>You can also store more complex data in the KeyVault. My implementation does not support it, but yours maybe should.</li>
</ol>
<p>Have fun! I hope you enjoyed reading this, learned a little and I could save you some time finding out how to work with Azure.</p>
</description>
</item>
</channel>
</rss>