GPG signing RPMs unattended

« previous entry | next entry »
Aug. 20th, 2008 | 10:55 am

At my office we package our work on Fedora systems into RPMs. I have been trying to cobble a build server to take our work from Subversion, build it, and upload it to our office's Yum repository. Sounds easy.

Fedora provides a lot of packages for automating a build server. Unfortunately, one of the final steps in the process of signing the packages with a GPG secret key cannot be automated. I've tried using an empty passphrase, and using the gpg-agent feature of GnuPG. I had to write a wrapper to fake sending the passphrase, see below.

My understanding of the problem is that RPM uses the getpass function to get the key passphrase and there's no way around this.

According to package maintainer documentation on the the Fedora Project Web site:

A Release Engineer [signs] and pushes out your updates. The signing step is currently a manual process, so your updates will not be instantly released once submitted to bodhi.

Related, Fedora is working on a putting together signing server. Apparently, they've had issues with "separating 'Who can sign' from 'Who knows the gpg passphrase'".

To get around this RPM flaw, I wrote an Expect wrapper to automatically sign RPM packages. I'm not an expert at Expect programming, but fortunately autoexpect helps.

Using the wrapper is as simple as the RPM signing command.

  $ ./rpm-sign.exp PACKAGE-FILE

Here's the script.

  #!/usr/bin/expect -f
  
  ### rpm-sign.exp -- Sign RPMs by sending the passphrase.
   
  spawn rpm --addsign {*}$argv
  expect -exact "Enter pass phrase: "
  send -- "Secret passphrase\r"
  expect eof
  
  ## end of rpm-sign.exp

Thank goodness for Tcl hackers.

Link | Leave a comment | Add to Memories | Share

Comments {14}

Great tip!

from: blog.nixpanic.net
date: Feb. 3rd, 2009 03:18 pm (UTC)
Link

Thanks for sharing this, exactly what I was looking for.

Cheers,
Niels

Reply | Thread

Re: Great tip!

from: anonymous
date: Feb. 13th, 2009 03:51 pm (UTC)
Link

I can run this script and it seems to be executing without error. But my rpm file remains unsigned.

My script is:

#!/usr/bin/expect -f
exp_internal 1
### rpm-sign.exp -- Sign RPMs by sending the passphrase.

spawn rpm --addsign $argv
expect "Enter pass phrase: " { send "\r" }
expect "Pass phrase is good." { }
expect ":" { close }

## end of rpm-sign.exp


I run rpm --checksig before and after:

[builder@air 021215-1116_DAP]$ rpm --checksig gsReporter-2.0.00.20-1.i686.rpm
gsReporter-2.0.00.20-1.i686.rpm: sha1 md5 OK
[builder@air 021215-1116_DAP]$ ./rpm-sign.exp gsReporter-2.0.00.20-1.i686.rpm
spawn rpm --addsign gsReporter-2.0.00.20-1.i686.rpm
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {22425}

expect: does "" (spawn_id exp4) match glob pattern "Enter pass phrase: "? no
Enter pass phrase:
expect: does "Enter pass phrase: " (spawn_id exp4) match glob pattern "Enter pass phrase: "? yes
expect: set expect_out(0,string) "Enter pass phrase: "
expect: set expect_out(spawn_id) "exp4"
expect: set expect_out(buffer) "Enter pass phrase: "
send: sending "\r" to { exp4 }

expect: does "" (spawn_id exp4) match glob pattern "Pass phrase is good."? no


expect: does "\r\n" (spawn_id exp4) match glob pattern "Pass phrase is good."? no
Pass phrase is good.

expect: does "\r\nPass phrase is good.\r\n" (spawn_id exp4) match glob pattern "Pass phrase is good."? yes
expect: set expect_out(0,string) "Pass phrase is good."
expect: set expect_out(spawn_id) "exp4"
expect: set expect_out(buffer) "\r\nPass phrase is good."

expect: does "\r\n" (spawn_id exp4) match glob pattern ":"? no
gsReporter-2.0.00.20-1.i686.rpm:

expect: does "\r\ngsReporter-2.0.00.20-1.i686.rpm:\r\n" (spawn_id exp4) match glob pattern ":"? yes
expect: set expect_out(0,string) ":"
expect: set expect_out(spawn_id) "exp4"
expect: set expect_out(buffer) "\r\ngsReporter-2.0.00.20-1.i686.rpm:"
[builder@air 021215-1116_DAP]$ rpm --checksig gsReporter-2.0.00.20-1.i686.rpm
gsReporter-2.0.00.20-1.i686.rpm: sha1 md5 OK

-Any insight appreciated!
-Bernard

Reply | Parent | Thread

Aaron S. Hawley

Re: Great tip!

from: aaronhawley
date: Feb. 14th, 2009 07:52 am (UTC)
Link

Looks like your Expect script works. Have you signed RPMs before?

Reply | Parent | Thread

Re: Great tip!

from: anonymous
date: Feb. 19th, 2009 03:42 pm (UTC)
Link

Yeah, sorry - I forgot to mention in my post that I can sign the rpm successfully from the command line:
"rpm --addsign whatever.rpm" adds the gpg signature.

Reply | Parent | Thread

Aaron S. Hawley

Re: Great tip!

from: aaronhawley
date: Feb. 19th, 2009 04:26 pm (UTC)
Link

Strange. I'm not sure.

Though, I don't understand why you are using a different expect script then my own. Although, I'm not a Tcl nor an Expect expert, so I wouldn't know. It looks like you are, though.

Reply | Parent | Thread

Re: Great tip!

from: anonymous
date: Feb. 26th, 2009 05:04 pm (UTC)
Link

Expert? I wish. But I started mucking around with your script when I got errors running it:

extra characters after close-brace
while executing
"spawn rpm --addsign {*}$argv
expect -exact "Enter pass phrase: "
send -- "Secret passphrase\r"
expect eof

## end of rpm-sign.exp
"
(file "./rpm-sign.exp" line 5)

===
Now, changing the spawn line to:

spawn rpm --addsign $argv

makes everything work in my system. Thanks for your help! Looks like I mucked around way too much and screwed the pooch.

Reply | Parent | Thread

Aaron S. Hawley

Re: Great tip!

from: aaronhawley
date: Feb. 26th, 2009 11:35 pm (UTC)
Link

Ahh, perhaps you're using an earlier version of Tcl. I remember seeing that the easier syntax for expanding argv was available with 8.5.

See: http://wiki.tcl.tk/17158

I'm sorry I wasn't more helpful. Happy to hear you solved it.

Reply | Parent | Thread

Aaron S. Hawley

Re: Great tip!

from: aaronhawley
date: Feb. 14th, 2009 07:54 am (UTC)
Link

Glad it was useful. Exactly the reason why I was sharing it.

Reply | Parent | Thread

Using your script for signing during build

from: anonymous
date: Apr. 17th, 2010 11:08 am (UTC)
Link

Hello Aaron,
thank you for the hint.
I was able to use your script to sign packages during the build.

Howto:
1) vi ~/bin/rpmbsign:
#!/usr/bin/expect --
spawn rpmbuild --sign {*}$argv
expect {
"^Enter pass phrase: " { send -- "Secret password\r" ; exp_continue }
eof
}


2) chmod +x ~/bin/rpmbsign

3) alias rpmbuild=rpmbsign

Please note the script shebang has got "#!/usr/bin/expect --" instead of "#!/usr/bin/expect -f".
It is there to be able to pass -ba parameter to the rpm and not pass it by expect itself.

Having two options gets rid of the errors when issuing commands where there is no password requested - for example rpmbuild --help .

Best regards
Michal Ambroz

Reply | Thread

Aaron S. Hawley

Re: Using your script for signing during build

from: aaronhawley
date: Apr. 17th, 2010 07:21 pm (UTC)
Link

Thanks for revisiting this. Good hack on passing options through to rpmbuild. I intended this for an automated batch process, so interactive use was never a priority. Glad to see it didn't take much to work.

I'd be afraid that having the --sign option on all the time for rpmbuild might cause problems. Since the only the rpmbuild does is build RPMs, then I guess it makes sense to make it the default action as you've done.

Thanks again for sharing.

Reply | Parent | Thread

Re: Using your script for signing during build

from: anonymous
date: Jun. 25th, 2010 09:11 pm (UTC)
Link

Hi Aaron,

Thanks for sharing this tip. This tip has already helped me a lot.

However, if I pass too many .rpm files (around 500) in one go to rpmsign command through expect it only accepts few of them. Looks like it hits the buffer of 8K. Any idea if we can increase the buffer or an alternative in which all file names can be passed in one go? I can loop over the list of files, but to reduce build time I want to pass the entire list to rpm sign so that passphrase verification is done only once.

Any comments?

Thanks!

Reply | Parent | Thread

Aaron S. Hawley

Re: Using your script for signing during build

from: aaronhawley
date: Jun. 27th, 2010 12:30 am (UTC)
Link

I'm not a Tcl hacker, so I'm not sure how to solve the buffer issues.

I'd say just pass your RPM file list on a pipe and send it to `xargs -s 8192 ./rpm-sign.exp' and let xargs manage the buffer. It can even run multiple processes at once, if given -P2 for example. This assumes you're building your RPM file arguments as a list of lines and have access to xargs.

Reply | Parent | Thread

(no subject)

from: anonymous
date: Aug. 10th, 2012 05:08 am (UTC)
Link

Thanks for this command. Would you happen to know how to pass quotes arguments from the program so that its part of $argv
I am defining _topdir while calling the script and no matter what I tried; its just putting a {} covering the arguments if it includes "
I am lost on what is the correct syntax. Something like
./rpmmodbuild --define "_topdir=" other options.

When it comes to spawn its like
rpmbuild --sign --define {_topdir=} options

So the command is not properly executed

Reply | Thread

(no subject)

from: ext_2058193
date: Jul. 9th, 2013 06:03 pm (UTC)
Link

I had an annoying issue with this, some packages were not being signed. Turned out to be a problem with expect's default timeout, it would end after 10s and the packages wouldn't get signed.
Adding "set timeout -1" to set an infinite timeout or "set timeout N" fixed this problem.

Reply | Thread