Tuesday, March 1, 2011

Using Hudson webhooks and wget for automated builds.

This command works pretty well for Hudson builds. It pipes the stdout results to /dev/null and remaps stderr too:

wget -O - --auth-no-challenge --user=hudsonuser --password=mypassword http://hudson.myapp.com/build?token=MYTOKEN > /dev/null 2>&1

Requirements:

1. You need to setup a Hudson user that has access permission to create new builds.

2. The --user and --password need to be set in the wget.

One puzzling issue that I encountered is that on wget 1.11.1, you will get Forbidden 403 errors when using this URL format:
wget -O - http://hudsonuser:mypassword@hudson.myapp.com/build?token=MYTOKEN 
But you still get a Forbidden 403 error when using this command:
wget -O - --auth-no-challenge http://hudson@mypassword:hudson.myapp.com/build?token=MYTOKEN
Why is this case? To find out, you can download the code from ftp://ftp.gnu.org/gnu/wget/ to understand. The issue actually turns out to be a bug in wget 1.11.1 that was later fixed (perhaps in 1.12?). Parsing of the URL happens in wget.c by setting the uname_b pointer and then storing the results of parse_credentials into the &user and &passwd, which then get stored as u->user and u->passwd.

src/url.c:
uname_b = p;
  p = url_skip_credentials (p);
  uname_e = p;

 if (uname_b != uname_e)
    { 
      /* http://user:pass@host */
      /*        ^         ^    */
      /*     uname_b   uname_e */
      if (!parse_credentials (uname_b, uname_e - 1, &user, &passwd))
        { 
          error_code = PE_INVALID_USER_NAME;
          goto error;
        }
    }

In Wget 1.11.1, there is an extra condition such that if the username is encoded in the URL format, then it will not issue the basic authentication code. This explains why having anything encoded in the URL string for username/password does not work:

/* Find the username and password for authentication. */
  user = u->user;
  passwd = u->passwd;
  search_netrc (u->host, (const char **)&user, (const char **)&passwd, 0);
  user = user ? user : (opt.http_user ? opt.http_user : opt.user);
  passwd = passwd ? passwd : (opt.http_passwd ? opt.http_passwd : opt.passwd);

  if (user && passwd
      && !u->user) /* We only do "site-wide" authentication with "global"
                      user/password values; URL user/password info overrides. */
    { 
      /* If this is a host for which we've already received a Basic
       * challenge, we'll go ahead and send Basic authentication creds. */
      basic_auth_finished = maybe_send_basic_creds(u->host, user, passwd, req);
    }

We can see in Wget 1.10.2 (src/http.c), there is no such check:

/* Find the username and password for authentication. */
  user = u->user;
  passwd = u->passwd;
  search_netrc (u->host, (const char **)&user, (const char **)&passwd, 0);
  user = user ? user : (opt.http_user ? opt.http_user : opt.user);
  passwd = passwd ? passwd : (opt.http_passwd ? opt.http_passwd : opt.passwd);

  if (user && passwd)
    { 
      /* We have the username and the password, but haven't tried
         any authorization yet.  Let's see if the "Basic" method
         works.  If not, we'll come back here and construct a
         proper authorization method with the right challenges.

         If we didn't employ this kind of logic, every URL that
         requires authorization would have to be processed twice,
         which is very suboptimal and generates a bunch of false
         "unauthorized" errors in the server log.

         #### But this logic also has a serious problem when used
         with stronger authentications: we *first* transmit the
         username and the password in clear text, and *then* attempt a
         stronger authentication scheme.  That cannot be right!  We
         are only fortunate that almost everyone still uses the
         `Basic' scheme anyway.

         There should be an option to prevent this from happening, for
         those who use strong authentication schemes and value their
         passwords.  */
      request_set_header (req, "Authorization",
                          basic_authentication_encode (user, passwd),
                          rel_value);

But on wget-1.12, extra allowances are made for the --auth-no-challenge flag.

/* Find the username and password for authentication. */
  user = u->user;
  passwd = u->passwd;
  search_netrc (u->host, (const char **)&user, (const char **)&passwd, 0);
  user = user ? user : (opt.http_user ? opt.http_user : opt.user);
  passwd = passwd ? passwd : (opt.http_passwd ? opt.http_passwd : opt.passwd);

  /* We only do "site-wide" authentication with "global" user/password
   * values unless --auth-no-challange has been requested; URL user/password
   * info overrides. */
  if (user && passwd && (!u->user || opt.auth_without_challenge))
    { 
      /* If this is a host for which we've already received a Basic
       * challenge, we'll go ahead and send Basic authentication creds. */
      basic_auth_finished = maybe_send_basic_creds(u->host, user, passwd, req);
    }

Note: it turns out Wget 1.10.2 and previous versions always attempts basic HTTP authentication, which can be a security hole because username/passwords are sent in the clear. Wget 1.11.1 does not implement this behavior. Hudson does not try to send an HTTP negotiation and immediately throws a 403 Forbidden error (See http://wiki.hudson-ci.org/display/HUDSON/Authenticating+scripted+clients). The reason we need --auth-no-challenge is to force later wget versions to send HTTP authentication info regardless of negotiation:

--auth-no-challenge
           If this option is given, Wget will send Basic HTTP authentication information (plaintext username and password) for all requests, just like Wget 1.10.2 and prior did by default.

           Use of this option is not recommended, and is intended only to support some few obscure servers, which never send HTTP authentication challenges, but accept unsolicited auth info, say, in
           addition to form-based authentication.

So even if we provide the --auth-no-challenge, we still can't use the URL format specified by wget in http://www.gnu.org/software/wget/manual/wget.html#URL-Format for Hudson automated builds, at least for wget 1.11.1. You have to upgrade to wget 1.12 to avoid this issue.

1 comment: