fastlane-plugin-secrets_manager_storage 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c6a3a917118758a6ec5691982284ab3bc62b02fe72ad11737e50de95531d9214
4
+ data.tar.gz: 33732acf2e3e32a3158ac0c1be29e2c4a5cb906d19b47bc609a0f221db4af0d2
5
+ SHA512:
6
+ metadata.gz: c71eaeda131b692b41f01a4b5f4e198dffd8c55eeb055314d067928a6fc6b89690a3f1c06626370b48091ca20f1d43ca1dc3ebb12a1593bbfee6dd0ad8cfbb73
7
+ data.tar.gz: 5574fd8adcf85f0bebddd7628792b6e025c4fb78830b3555091f96c8139f40ca7d719fd62013d9a263fee7551897cff26d76473d202087568ae8d2e090a7e7e6
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Secrets Manager Storage
2
+
3
+ This plugin enables Fastlane users to store their provisioning profiles and certificates securely in
4
+ AWS Secrets Manager by adding a `secrets_manager` storage backend to Fastlane match.
5
+
6
+ [![Build Status][ci-image]][ci-url] [![License][license-image]][license-url]
7
+ [![Developed at Klarna][klarna-image]][klarna-url]
8
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-secrets_manager_storage)
9
+
10
+ Reasons to use this (compared to the git or s3 backend):
11
+
12
+ - certificates are stored securley (always encrypted) by default
13
+ - all access is controlled via AWS IAM and is fine-grained:
14
+ - users can be granted access to review the secret's metadata separate from the ability to read
15
+ the actual, unencrypted values
16
+ - no need to manage a `MATCH_PASSWORD` – just use your existing AWS access controls
17
+ - all access to the decrypted secrets is logged into AWS CloudTrail, providing an audit-trail to
18
+ access
19
+ - Secret lifecycle can be tracked independently of Fastlane, enabling you to have alerts on secret
20
+ age by using the secret's version metadata (e.g. Created On)
21
+
22
+ > :information_source: Fastlane plugins are only automatically loaded when using a Fastfile. This
23
+ > means that using a Matchfile or `fastlane match` commands will not work with this storage backing.
24
+ > We're happy to take contributions but we've always ended up writing Fastlane actions in our
25
+ > projects anyway (not using the `match` commands or `Matchfile`)
26
+
27
+ ## Getting Started
28
+
29
+ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with
30
+ `fastlane-plugin-secrets_manager_storage`, add it to your project by running:
31
+
32
+ ```bash
33
+ fastlane add_plugin secrets_manager_storage
34
+ ```
35
+
36
+ You will then need to modify your Fastfile to have actions which use match/sync_code_signing use the
37
+ `secrets_manager` storage backend. You can look in [fastlane/Fastfile](fastlane/Fastfile) in this
38
+ repository for example use.
39
+
40
+ You will need to ensure that you have properly configured the environment to be able to access
41
+ Secrets Manager. If you use IAM Users then you may want to set `AWS_ACCESS_KEY_ID` and
42
+ `AWS_SECRET_ACCESS_KEY_ID` appropriately. AWS documentation
43
+ [explains how credentials are loaded](https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html).
44
+
45
+ ## Formatting
46
+
47
+ This project is formatted using Prettier. Simply run `rake prettier' to format
48
+
49
+ ```
50
+ rake prettier
51
+ ```
52
+
53
+ ## Development setup
54
+
55
+ ```sh
56
+ bundle install
57
+ yarn install
58
+ ```
59
+
60
+ ## How to contribute
61
+
62
+ See our guide on [contributing](.github/CONTRIBUTING.md).
63
+
64
+ ## Release History
65
+
66
+ See our [changelog](CHANGELOG.md).
67
+
68
+ ## About _fastlane_
69
+
70
+ _fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android
71
+ apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
72
+
73
+ ## License
74
+
75
+ Copyright © 2024 Klarna Bank AB
76
+
77
+ For license details, see the [LICENSE](LICENSE) file in the root of this project.
78
+
79
+ <!-- Markdown link & img dfn's -->
80
+
81
+ [ci-image]: https://img.shields.io/badge/build-passing-brightgreen?style=flat-square
82
+ [ci-url]: https://github.com/klarna-incubator/TODO
83
+ [license-image]: https://img.shields.io/badge/license-Apache%202-blue?style=flat-square
84
+ [license-url]: http://www.apache.org/licenses/LICENSE-2.0
85
+ [klarna-image]:
86
+ https://img.shields.io/badge/%20-Developed%20at%20Klarna-black?style=flat-square&labelColor=ffb3c7&logo=klarna&logoColor=black
87
+ [klarna-url]: https://klarna.github.io
@@ -0,0 +1,32 @@
1
+ require "fastlane/action"
2
+
3
+ module Fastlane
4
+ module Actions
5
+ class SecretsManagerStorageAction < Action
6
+ def self.run(params)
7
+ UI.message(
8
+ "If you're running this action, you shouldn't be. This action only exists because Fastlane expects all plugins to have actions. See the README.md for secrets_manager_storage to understand how to set the match storage",
9
+ )
10
+ end
11
+
12
+ def self.description
13
+ "This action is not necessary and is unused – see the README for secrets_manager_storage to learn how to use it. Fastlane expects all plugins to have actions."
14
+ end
15
+
16
+ def self.authors
17
+ ["Case Taintor"]
18
+ end
19
+
20
+ def self.return_value
21
+ end
22
+
23
+ def self.available_options
24
+ []
25
+ end
26
+
27
+ def self.is_supported?(platform)
28
+ true
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,237 @@
1
+ require "fastlane_core/command_executor"
2
+ require "fastlane_core/configuration/configuration"
3
+ require "match"
4
+ require "fileutils"
5
+ require "aws-sdk-secretsmanager"
6
+
7
+ module Fastlane
8
+ module SecretsManagerStorage
9
+ class Storage < ::Match::Storage::Interface
10
+ attr_reader :path_prefix
11
+ attr_reader :tags
12
+ attr_reader :region
13
+ attr_reader :git_url
14
+ attr_reader :username
15
+ attr_reader :readonly
16
+ attr_reader :team_id
17
+ attr_reader :team_name
18
+ attr_reader :api_key_path
19
+ attr_reader :api_key
20
+
21
+ def self.configure(params)
22
+ if params[:git_url].to_s.length > 0
23
+ UI.important("Looks like you still define a `git_url` somewhere, even though")
24
+ UI.important("you use AWS Secrets Manager. You can remove the `git_url`")
25
+ UI.important("from your Matchfile and Fastfile")
26
+ UI.message("The above is just a warning, fastlane will continue as usual now...")
27
+ end
28
+
29
+ return(
30
+ self.new(
31
+ path_prefix: params[:secrets_manager_path_prefix],
32
+ tags: params[:secrets_manager_tags],
33
+ region: params[:secrets_manager_region],
34
+ username: params[:username],
35
+ readonly: params[:readonly],
36
+ team_id: params[:team_id],
37
+ team_name: params[:team_name],
38
+ api_key_path: params[:api_key_path],
39
+ api_key: params[:api_key],
40
+ )
41
+ )
42
+ end
43
+
44
+ def initialize(
45
+ path_prefix: nil,
46
+ tags: {},
47
+ region: nil,
48
+ username: nil,
49
+ readonly: nil,
50
+ team_id: nil,
51
+ team_name: nil,
52
+ api_key_path: nil,
53
+ api_key: nil
54
+ )
55
+ @path_prefix = path_prefix
56
+ @tags = tags
57
+ @region = region || ENV["AWS_REGION"]
58
+ @username = username
59
+ @readonly = readonly
60
+ @team_id = team_id
61
+ @team_name = team_name
62
+ @api_key_path = api_key_path
63
+ @api_key = api_key
64
+
65
+ @client = Aws::SecretsManager::Client.new(region: region)
66
+ UI.message("Initializing match for AWS Secrets Manager at #{@path_prefix} in #{@region}")
67
+ end
68
+
69
+ # To make debugging easier, we have a custom exception here
70
+ def prefixed_working_directory
71
+ # We fall back to "*", which means certificates and profiles
72
+ # from all teams that use this bucket would be installed. This is not ideal, but
73
+ # unless the user provides a `team_id`, we can't know which one to use
74
+ # This only happens if `readonly` is activated, and no `team_id` was provided
75
+ @_folder_prefix ||= currently_used_team_id
76
+ if @_folder_prefix.nil?
77
+ # We use a `@_folder_prefix` variable, to keep state between multiple calls of this
78
+ # method, as the value won't change. This way the warning is only printed once
79
+ UI.important(
80
+ "Looks like you run `match` in `readonly` mode, and didn't provide a `team_id`. This will still work, however it is recommended to provide a `team_id` in your Appfile or Matchfile",
81
+ )
82
+ @_folder_prefix = "*"
83
+ end
84
+ return File.join(working_directory, @_folder_prefix)
85
+ end
86
+
87
+ def download
88
+ return if @working_directory
89
+
90
+ self.working_directory = Dir.mktmpdir
91
+
92
+ next_token = nil
93
+ secret_names = []
94
+ with_aws_authentication_error_handling do
95
+ loop do
96
+ resp =
97
+ @client.list_secrets(
98
+ { next_token: next_token, filters: [{ key: "name", values: [@path_prefix] }] },
99
+ )
100
+ resp.secret_list.each { |secret| secret_names << secret.name }
101
+ next_token = resp.next_token
102
+ break if next_token.nil?
103
+ end
104
+
105
+ secret_names.each do |name|
106
+ secret = @client.get_secret_value({ secret_id: name })
107
+ filename = File.join(self.working_directory, name.delete_prefix(self.path_prefix))
108
+ FileUtils.mkdir_p(File.dirname(filename))
109
+ IO.binwrite(filename, secret.secret_binary)
110
+ end
111
+ end
112
+
113
+ UI.verbose(
114
+ "Successfully downloaded all Secrets from AWS Secrets Manager to #{self.working_directory}",
115
+ )
116
+ end
117
+
118
+ def currently_used_team_id
119
+ if self.readonly
120
+ # In readonly mode, we still want to see if the user provided a team_id
121
+ # see `prefixed_working_directory` comments for more details
122
+ return self.team_id
123
+ else
124
+ if self.team_id.to_s.empty?
125
+ UI.user_error!(
126
+ "The `team_id` option is required. fastlane cannot automatically determine portal team id via the App Store Connect API (yet)",
127
+ )
128
+ end
129
+
130
+ spaceship =
131
+ ::Match::SpaceshipEnsure.new(self.username, self.team_id, self.team_name, api_token)
132
+ return spaceship.team_id
133
+ end
134
+ end
135
+
136
+ def api_token
137
+ api_token =
138
+ Spaceship::ConnectAPI::Token.from(hash: self.api_key, filepath: self.api_key_path)
139
+ api_token ||= Spaceship::ConnectAPI.token
140
+ return api_token
141
+ end
142
+
143
+ # Returns a short string describing + identifying the current
144
+ # storage backend. This will be printed when nuking a storage
145
+ def human_readable_description
146
+ "AWS Secrets Manager Storage [#{self.path_prefix}]"
147
+ end
148
+
149
+ def upload_files(files_to_upload: [], custom_message: nil)
150
+ # `files_to_upload` is an array of files that need to be uploaded to AWS Secrets Manager
151
+ # Those doesn't mean they're new, it might just be they're changed
152
+ # Either way, we'll upload them using the same technique
153
+
154
+ files_to_upload.each do |current_file|
155
+ # Go from
156
+ # "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/profiles/development/Development_me.mobileprovision"
157
+ # to
158
+ # "profiles/development/Development_me.mobileprovision"
159
+ #
160
+
161
+ # We also remove the trailing `/`
162
+ secret_name = current_file.delete_prefix(self.working_directory)
163
+ UI.verbose("Uploading '#{secret_name}' to AWS Secrets Manager...")
164
+ create_or_update_secret(current_file, secret_name)
165
+ end
166
+ end
167
+
168
+ def delete_files(files_to_delete: [], custom_message: nil)
169
+ files_to_delete.each do |current_file|
170
+ secret_name = current_file.delete_prefix(self.working_directory + "/")
171
+
172
+ delete_secret(secret_name)
173
+ end
174
+ end
175
+
176
+ def skip_docs
177
+ true
178
+ end
179
+
180
+ def list_files(file_name: "", file_ext: "")
181
+ Dir[File.join(working_directory, self.team_id, "**", file_name, "*.#{file_ext}")]
182
+ end
183
+
184
+ def generate_matchfile_content(template: nil)
185
+ # Will implement once I figure out how to have a plugin with `match` commands
186
+ raise "Not Implemented"
187
+ end
188
+
189
+ def create_or_update_secret(current_file, secret_name)
190
+ full_secret_path = generate_secret_path(secret_name)
191
+ begin
192
+ @client.describe_secret(secret_id: full_secret_path)
193
+ UI.verbose("Secret '#{secret_name}' already exists, updating...")
194
+ @client.put_secret_value(
195
+ secret_id: full_secret_path,
196
+ secret_binary: IO.binread(current_file),
197
+ )
198
+ rescue Aws::SecretsManager::Errors::ResourceNotFoundException
199
+ UI.verbose("Secret '#{secret_name}' doesn't exist, creating...")
200
+ @client.create_secret(
201
+ name: full_secret_path,
202
+ secret_binary: File.open(current_file, "rb").read,
203
+ tags: generate_tags_in_aws_format(tags),
204
+ )
205
+ end
206
+ end
207
+
208
+ def delete_secret(secret_name)
209
+ @client.delete_secret({ secret_id: secret_name, recovery_window_in_days: 7 })
210
+ rescue Aws::SecretsManager::Errors::ResourceNotFoundException
211
+ UI.verbose("Secret '#{secret_name}' doesn't exist, skipping...")
212
+ end
213
+
214
+ private
215
+
216
+ def generate_secret_path(secret_name)
217
+ prefix = path_prefix
218
+ prefix += "/" unless secret_name.start_with?("/")
219
+ "#{prefix}#{secret_name}"
220
+ end
221
+
222
+ def generate_tags_in_aws_format(tags)
223
+ tags.map { |key, value| { key: key, value: value } }
224
+ end
225
+
226
+ def with_aws_authentication_error_handling
227
+ explainer =
228
+ "Note: AWS credentials are passed via environment variables. The AWS Ruby SDK documentation explains which environment variables are expected – https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html"
229
+ yield
230
+ rescue Aws::SecretsManager::Errors::ExpiredTokenException,
231
+ Aws::Errors::MissingCredentialsError => e
232
+ UI.error("AWS Secrets Manager authentication error: #{e}.\n\n#{explainer}")
233
+ raise e
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module SecretsManagerStorage
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,73 @@
1
+ require "fastlane/plugin/secrets_manager_storage/version"
2
+ require_relative "./secrets_manager_storage/storage"
3
+
4
+ Match::Storage.register_backend(
5
+ type: "secrets_manager",
6
+ storage_class: ::Fastlane::SecretsManagerStorage::Storage,
7
+ )
8
+ Match::Encryption.register_backend(type: "secrets_manager") { nil }
9
+
10
+ # At the time of writing, Match::Options.append_option exists but assumes available_options memoizes (but it doesn't)
11
+ # so we'll fix it to memoize
12
+ module MemoizeAvailableOptions
13
+ def available_options
14
+ @available_options ||= super
15
+ end
16
+ end
17
+ Match::Options.singleton_class.prepend(MemoizeAvailableOptions)
18
+
19
+ Match::Options.append_option(
20
+ FastlaneCore::ConfigItem.new(
21
+ key: :secrets_manager_path_prefix,
22
+ env_name: "MATCH_SECRETS_MANAGER_PATH_PREFIX",
23
+ description: "The prefix to be used for all Secrets Manager Secrets",
24
+ optional: true,
25
+ type: String,
26
+ )
27
+ )
28
+ Match::Options.append_option(
29
+ FastlaneCore::ConfigItem.new(
30
+ key: :secrets_manager_tags,
31
+ env_name: "MATCH_SECRETS_MANAGER_TAGS",
32
+ description: "tags which are used when creating a new secret in Secrets Manager",
33
+ optional: true,
34
+ type: Hash,
35
+ )
36
+ )
37
+ Match::Options.append_option(
38
+ FastlaneCore::ConfigItem.new(
39
+ key: :secrets_manager_region,
40
+ env_name: "MATCH_SECRETS_MANAGER_REGION",
41
+ description: "The prefix to be used for all Secrets Manager Secrets",
42
+ optional: true,
43
+ type: String,
44
+ )
45
+ )
46
+
47
+ # Fastlane will complain if a plugin doesn't include any actions. Thus, we have to include an action in the right way
48
+ # so it won't complain
49
+ module Fastlane
50
+ module SecretsManagerStorage
51
+ def self.all_classes
52
+ Dir[File.expand_path("**/{actions}/*.rb", File.dirname(__FILE__))]
53
+ end
54
+ end
55
+ end
56
+
57
+ # By default we want to import all available actions and helpers
58
+ # A plugin can contain any number of actions and plugins
59
+ Fastlane::SecretsManagerStorage.all_classes.each { |current| require current }
60
+
61
+ # At the time of writing, Fastlane/Match does not actually support adding backends via a plugin since it hard-codes the
62
+ # allowed storage modes. This code simply monkey-patches Fastlane/Match to respond with all registered storage modes
63
+ module Match
64
+ def self.storage_modes
65
+ Storage.backends.keys
66
+ end
67
+
68
+ class Setup
69
+ def storage_options
70
+ ::Match::Storage.backends.keys
71
+ end
72
+ end
73
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-secrets_manager_storage
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Case Taintor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-02-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-secretsmanager
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ description:
28
+ email: case.taintor@klarna.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - LICENSE
34
+ - README.md
35
+ - lib/fastlane/plugin/secrets_manager_storage.rb
36
+ - lib/fastlane/plugin/secrets_manager_storage/actions/secrets_manager_storage_action.rb
37
+ - lib/fastlane/plugin/secrets_manager_storage/storage.rb
38
+ - lib/fastlane/plugin/secrets_manager_storage/version.rb
39
+ homepage: https://github.com/klarna-incubator/fastlane-plugin-secrets_manager_storage
40
+ licenses:
41
+ - Apache-2.0
42
+ metadata:
43
+ rubygems_mfa_required: 'true'
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '2.6'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.4.10
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Enables fastlane match to use AWS Secrets Manager as backing storage
63
+ test_files: []