Class: Pupistry::GPG
- Inherits:
-
Object
- Object
- Pupistry::GPG
- Defined in:
- lib/pupistry/gpg.rb
Overview
Pupistry::GPG
Instance Attribute Summary collapse
-
#checksum ⇒ Object
All the functions needed for manipulating the GPG signatures.
-
#signature ⇒ Object
Returns the value of attribute signature.
Instance Method Summary collapse
-
#artifact_sign ⇒ Object
Sign the artifact and return the signature.
-
#artifact_verify ⇒ Object
Verify the signature for a particular artifact.
-
#initialize(checksum) ⇒ GPG
constructor
A new instance of GPG.
-
#pubkey_exist? ⇒ Boolean
Check if the public key is installed on this machine?.
-
#pubkey_install ⇒ Object
Install the public key.
-
#pubkey_upload ⇒ Object
Extract & upload the public key to the s3 bucket for other users.
-
#signature_cleanup ⇒ Object
Generally we should clean up old signature files before and after using them.
-
#signature_extract ⇒ Object
Extract the signature from the manifest file and write it to file in native binary format.
-
#signature_save ⇒ Object
Save the signature into the manifest file.
Constructor Details
#initialize(checksum) ⇒ GPG
Returns a new instance of GPG.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/pupistry/gpg.rb', line 16 def initialize(checksum) # Need a checksum to do signing for if checksum @checksum = checksum else $logger.fatal 'Probable bug, need a checksum provided with GPG validation' exit 0 end # Make sure that we have GPG available unless system('gpg --version >> /dev/null 2>&1') # rubocop:disable Style/GuardClause $logger.fatal "'gpg' command is not available, unable to do any signature creation or verification." exit 0 end end |
Instance Attribute Details
#checksum ⇒ Object
All the functions needed for manipulating the GPG signatures
13 14 15 |
# File 'lib/pupistry/gpg.rb', line 13 def checksum @checksum end |
#signature ⇒ Object
Returns the value of attribute signature.
14 15 16 |
# File 'lib/pupistry/gpg.rb', line 14 def signature @signature end |
Instance Method Details
#artifact_sign ⇒ Object
Sign the artifact and return the signature. Does not validation of the signature.
false Failure base64 Encoded signature
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 |
# File 'lib/pupistry/gpg.rb', line 37 def artifact_sign @signature = 'unsigned' # Clean up the existing signature file signature_cleanup Dir.chdir("#{$config['general']['app_cache']}/artifacts/") do # Generate the signature file and pick up the signature data unless system "gpg --use-agent --detach-sign artifact.#{@checksum}.tar.gz" $logger.error 'Unable to sign the artifact, an unexpected failure occured. No file uploaded.' return false end if File.exist?("artifact.#{@checksum}.tar.gz.sig") $logger.info 'A signature file was successfully generated.' else $logger.error 'A signature file was NOT generated.' return false end # Convert the signature into base64. It's easier to bundle all the # metadata into a single file and extracting it out when needed, than # having to keep track of yet-another-file. Because we encode into # ASCII here, no need to call GPG with --armor either. @signature = Base64.encode64(File.read("artifact.#{@checksum}.tar.gz.sig")) unless @signature $logger.error 'An unexpected issue occured and no signature was generated' return false end end # Make sure the public key has been uploaded if it hasn't already pubkey_upload @signature end |
#artifact_verify ⇒ Object
Verify the signature for a particular artifact.
true Signature is legit false Signature is invalid (security issue!)
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 |
# File 'lib/pupistry/gpg.rb', line 81 def artifact_verify Dir.chdir("#{$config['general']['app_cache']}/artifacts/") do if File.exist?("artifact.#{@checksum}.tar.gz.sig") $logger.debug 'Signature already extracted on disk, running verify....' else $logger.debug 'Extracting signature from manifest data...' signature_extract end # Verify the signature pubkey_install unless pubkey_exist? output_verify = `gpg --quiet --status-fd 1 --verify artifact.#{@checksum}.tar.gz.sig 2>&1` # Cleanup on disk file signature_cleanup # Was it valid? output_verify.each_line do |line| if /\[GNUPG:\]\sGOODSIG\s[A-Z0-9]*#{$config["general"]["gpg_signing_key"]}\s/.match(line) $logger.info "Artifact #{@checksum} has a valid signature belonging to #{$config['general']['gpg_signing_key']}" return true end if /\[GNUPG:\]\sBADSIG\s/.match(line) $logger.fatal "Artifact #{@checksum} has AN INVALID GPG SECURITY SIGNATURE and could be CORRUPT or TAMPERED with." exit 0 end end # Unexpected error $logger.error 'An unexpected validation issue occured, see below debug information:' output_verify.each_line do |line| $logger.error "GPG: #{line}" end end # Something went wrong $logger.fatal "Artifact #{@checksum} COULD NOT BE GPG VALIDATED and could be CORRUPT or TAMPERED with." exit 0 end |
#pubkey_exist? ⇒ Boolean
Check if the public key is installed on this machine?
175 176 177 178 179 180 181 182 183 184 |
# File 'lib/pupistry/gpg.rb', line 175 def pubkey_exist? # We prefix with 0x to avoid matching on strings in key names if system "gpg --status-fd a --list-keys 0x#{$config['general']['gpg_signing_key']} 2>&1 >> /dev/null" $logger.debug 'Public key exists on this system' return true else $logger.debug 'Public key does not exist on this system' return false end end |
#pubkey_install ⇒ Object
Install the public key. This is a potential avenue for exploit, if a machine is being built for the first time, it has no existing trust of the GPG key, other than transit encryption to the S3 bucket. To protect against attacks at the bootstrap time, you should pre-load your machine images with the public GPG key.
For those users who trade off some security for convienence, we install the GPG public key for them direct from the S3 repo.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/pupistry/gpg.rb', line 230 def pubkey_install $logger.warn "Installing GPG key #{$config['general']['gpg_signing_key']}..." s3 = Pupistry::StorageAWS.new 'agent' unless s3.download "#{$config['general']['gpg_signing_key']}.publickey", "#{$config['general']['app_cache']}/artifacts/#{$config['general']['gpg_signing_key']}.publickey" $logger.error 'Unable to download GPG key from S3 bucket, this will prevent validation of signature' return false end unless system "gpg --import < #{$config['general']['app_cache']}/artifacts/#{$config['general']['gpg_signing_key']}.publickey > /dev/null 2>&1" $logger.error 'A fault occured when trying to import the GPG key' return false end rescue StandardError $logger.error 'Something unexpected occured when installing the GPG public key' return false end |
#pubkey_upload ⇒ Object
Extract & upload the public key to the s3 bucket for other users
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 |
# File 'lib/pupistry/gpg.rb', line 188 def pubkey_upload unless File.exist?("#{$config['general']['app_cache']}/artifacts/#{$config['general']['gpg_signing_key']}.publickey") # GPG key does not exist locally, we therefore assume it's not in the S3 # bucket either, so we should export out and upload. Technically this may # result in a few extra uploads (once for any new machine using Pupistry) # but it doesn't cause any issue and saves me writing more code ;-) $logger.info "Exporting GPG key #{$config['general']['gpg_signing_key']} and uploading to S3 bucket..." # If it doesn't exist on this machine, then we're a bit stuck! unless pubkey_exist? $logger.error "The public key #{$config['general']['gpg_signing_key']} does not exist on this system, so unable to export it out" return false end # Export out key unless system "gpg --export --armour 0x#{$config['general']['gpg_signing_key']} > #{$config['general']['app_cache']}/artifacts/#{$config['general']['gpg_signing_key']}.publickey" $logger.error 'A fault occured when trying to export the GPG key' return false end # Upload s3 = Pupistry::StorageAWS.new 'build' unless s3.upload "#{$config['general']['app_cache']}/artifacts/#{$config['general']['gpg_signing_key']}.publickey", "#{$config['general']['gpg_signing_key']}.publickey" $logger.error 'Unable to upload GPG key to S3 bucket' return false end end end |
#signature_cleanup ⇒ Object
Generally we should clean up old signature files before and after using them
126 127 128 |
# File 'lib/pupistry/gpg.rb', line 126 def signature_cleanup FileUtils.rm("#{$config['general']['app_cache']}/artifacts/artifact.#{@checksum}.tar.gz.sig", force: true) end |
#signature_extract ⇒ Object
Extract the signature from the manifest file and write it to file in native binary format.
false Unable to extract unsigned Manifest shows that the artifact is not signed base64 Encoded signature
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/pupistry/gpg.rb', line 136 def signature_extract manifest = YAML.load(File.open($config['general']['app_cache'] + "/artifacts/manifest.#{@checksum}.yaml"), safe: true, raise_on_unknown_tag: true) if manifest['gpgsig'] # We have the base64 version @signature = manifest['gpgsig'] # Decode the base64 and write the signature file File.write("#{$config['general']['app_cache']}/artifacts/artifact.#{@checksum}.tar.gz.sig", Base64.decode64(@signature)) return @signature else return false end rescue StandardError => e $logger.error 'Something unexpected occured when reading the manifest file' raise e end |
#signature_save ⇒ Object
Save the signature into the manifest file
158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/pupistry/gpg.rb', line 158 def signature_save manifest = YAML.load(File.open($config['general']['app_cache'] + "/artifacts/manifest.#{@checksum}.yaml"), safe: true, raise_on_unknown_tag: true) manifest['gpgsig'] = @signature File.open("#{$config['general']['app_cache']}/artifacts/manifest.#{@checksum}.yaml", 'w') do |fh| fh.write YAML.dump(manifest) end return true rescue StandardError $logger.error 'Something unexpected occured when updating the manifest file with GPG signature' return false end |