Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Environment variable inheritance using MDNotifyCmd and MDMessageCmd #319

Closed
andreblanke opened this issue Aug 14, 2023 · 4 comments
Closed

Comments

@andreblanke
Copy link

Using httpd v2.4.57 and mod_md v2.4.21 on Linux 6.4.10, I've been trying to gracefully reload httpd from a simple script executed by MDNotifyCmd or MDMessageCmd:

#!/usr/bin/env sh
/usr/local/apache2/bin/apachectl -k graceful

However, it appears that environment variables are not correctly inherited by the child process, resulting in warnings akin to

AH00111: Config variable ${ENV_VAR} is not defined

because my httpd.conf relies on certain environment variables being set. If I extend the above script to include an invocation of printenv, only PWD=/usr/local/apache2 is printed as environment variable which seems to confirm my assumption.

Issue #198 looks related, but it is resolved and inheritance appears to work correctly in that case.

Reproducible example

I have written a small example program which – at least on my machine – demonstrates the issue. It uses the Apache Portable Runtime and md_util_exec along with stubs copied from mod_md.

md_util_exec is used to invoke /usr/bin/printenv twice, passing ENV_VAR=value as environment variable first and NULL second. The following output is produced by the program:

Executing printenv with env={"ENV_VAR=value"}:
ENV_VAR=value
printenv finished with exit code 0

Executing printenv with env=NULL:
printenv finished with exit code 0

As you can see, neither of the process executions inherit the environment variables of the parent process. By changing the cmdtype passed to apr_procattr_cmdtype_set from APR_PROGRAM to APR_PROGRAM_ENV, both processes seem to inherit the environment of the parent, but the additional ENV_VAR=value set in the first configuration is not set for the child process and thus lost.

-           && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
+           && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM_ENV))
Source code
#include <apr-1/apr_thread_proc.h>

//region md_util_exec and stubs
typedef enum {
    MD_LOG_EMERG,
    MD_LOG_ALERT,
    MD_LOG_CRIT,
    MD_LOG_ERR,
    MD_LOG_WARNING,
    MD_LOG_NOTICE,
    MD_LOG_INFO,
    MD_LOG_DEBUG,
    MD_LOG_TRACE1,
    MD_LOG_TRACE2,
    MD_LOG_TRACE3,
    MD_LOG_TRACE4,
    MD_LOG_TRACE5,
    MD_LOG_TRACE6,
    MD_LOG_TRACE7,
    MD_LOG_TRACE8,
} md_log_level_t;

#define MD_LOG_MARK __FILE__,__LINE__

void md_log_perror(const char *file, int line, md_log_level_t level,
                   apr_status_t rv, apr_pool_t *p, const char *fmt, ...)
{
}

apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
                          apr_array_header_t *env, int *exit_code)
{
    apr_status_t rv;
    apr_procattr_t *procattr;
    apr_proc_t *proc;
    apr_exit_why_e ewhy;
    const char * const *envp = NULL;
    char buffer[1024];

    *exit_code = 0;
    if (!(proc = apr_pcalloc(p, sizeof(*proc)))) {
        return APR_ENOMEM;
    }
    if (env && env->nelts > 0) {
        apr_array_header_t *nenv;

        nenv = apr_array_copy(p, env);
        APR_ARRAY_PUSH(nenv, const char *) = NULL;
        envp = (const char * const *)nenv->elts;
    }
    if (   APR_SUCCESS == (rv = apr_procattr_create(&procattr, p))
           && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE,
                                                       APR_NO_PIPE, APR_FULL_BLOCK))
           && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
           && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, envp, procattr, p))) {

        /* read stderr and log on INFO for possible fault analysis. */
        while(APR_SUCCESS == (rv = apr_file_gets(buffer, sizeof(buffer)-1, proc->err))) {
            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "cmd(%s) stderr: %s", cmd, buffer);
        }
        if (!APR_STATUS_IS_EOF(rv)) goto out;
        apr_file_close(proc->err);

        if (APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) {
            /* let's not dwell on exit stati, but core should signal something's bad */
            if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) {
                return APR_EINCOMPLETE;
            }
            return APR_SUCCESS;
        }
    }
    out:
    return rv;
}
//endregion

int main() {
    const char *prog = "/usr/bin/printenv";
    const char *argv[] = {prog, NULL};

    apr_initialize();

    apr_pool_t *pool;
    apr_pool_create(&pool, NULL);

    int exit_code;
    {
        const char *env_var = "ENV_VAR=value";

        apr_array_header_t *env = apr_array_make(pool, 1, sizeof(char *));
        APR_ARRAY_PUSH(env, const char *) = env_var;

        printf("Executing printenv with env={\"%s\"}:\n", env_var);
        md_util_exec(pool, prog, argv, env, &exit_code);
        printf("printenv finished with exit code %d\n", exit_code);
    }
    printf("\n");
    {
        printf("Executing printenv with env=NULL:\n");
        md_util_exec(pool, prog, argv, NULL, &exit_code);
        printf("printenv finished with exit code %d\n", exit_code);
    }
    return 0;
}

Closing words

I don't really understand why the functionality looks to be working in #198, but it sadly isn't in my case unless I am missing something.

As a fix, it could be possible to implement a function to retrieve all environment variables for POSIX and WIN32, include ones relevant for mod_md, and then pass the new list of variables to md_util_exec. The function could be extracted to APR at a later time.

@icing
Copy link
Owner

icing commented Aug 15, 2023

Thanks for the detailed report. Looking at this, I believe md_util_exec should use APR_PROGRAM_ENV and remove its env parameter since any value given there will not have an effect then, as you observed.

In the case of #198, we tried to supply additional vars to the called process, but that resulted in the inherited environment of httpd to be completely replaced. Which did lead to the failures reported there.

Note that #198 was on Windows, where APR_PROGRAM and APR_PROGRAM_ENV have the same implementation. So it did not matter there.

As to your original intention of reloading Apache in such a program, that will not work in default Linux setups where root is needed for reloads, but the invoked program will run as an unprivileged user like www-data.

icing added a commit that referenced this issue Aug 15, 2023
- refs #319
- use APR_PROGRAM_ENV instead of APR_PROGRAM when executing
  commands like `MDMessageCmd`. This results in the httpd
  environment being inherited by the invoked process on
  operating systems like Linux.
  On others, like Windows, it makes no difference, looking
  at the implementation in the Apache Runtime.
@icing
Copy link
Owner

icing commented Aug 15, 2023

Could you give #320 a try?

@andreblanke
Copy link
Author

Note that #198 was on Windows, where APR_PROGRAM and APR_PROGRAM_ENV have the same implementation. So it did not matter there.

Ah, so that was confusing me.

As to your original intention of reloading Apache in such a program, that will not work in default Linux setups where root is needed for reloads, but the invoked program will run as an unprivileged user like www-data.

Thanks for the heads-up. My original plan was to give www-data permission to execute only /usr/local/apache2/apachectl -k graceful with elevated rights, but using ports other than 80 and 443 might be the more secure approach for my use case.

Could you give #320 a try?

The PR fixes the issue for me. Thank you for your quick help.

@icing
Copy link
Owner

icing commented Aug 16, 2023

Thanks for testing.

@icing icing closed this as completed Aug 16, 2023
asfgit pushed a commit to apache/httpd that referenced this issue Aug 16, 2023
   started via MDMessageCmd and MDChallengeDns01 on *nix system.
   See <icing/mod_md#319>.



git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1911721 13f79535-47bb-0310-9956-ffa450edef68
asfgit pushed a commit to apache/httpd that referenced this issue Aug 16, 2023
 * mod_md:
   - New directive `MDMatchNames all|servernames` to allow more control over how
     MDomains are matched to VirtualHosts.
   - New directive `MDChallengeDns01Version`. Setting this to `2` will provide
     the command also with the challenge value on `teardown` invocation. In version
     1, the default, only the `setup` invocation gets this parameter.
     Refs #312. Thanks to @domrim for the idea.
   - For Managed Domain in "manual" mode, the checks if all used ServerName and
     ServerAlias are part of the MDomain now reports a warning instead of an error
     (AH10040) when not all names are present.
   - MDChallengeDns01 can now be configured for individual domains.
     Using PR from Jérôme Billiras (@bilhackmac) and adding test case and fixing proper working
   - Fixed a bug found by Jérôme Billiras (@bilhackmac) that caused the challenge
     teardown not being invoked as it should.

 * mod_md: fixed passing of the server environment variables to programs
   started via MDMessageCmd and MDChallengeDns01 on *nix system.
   See <icing/mod_md#319>.



git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1911722 13f79535-47bb-0310-9956-ffa450edef68
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this issue Oct 19, 2023
Changes with Apache 2.4.58

  *) mod_ssl: Silence info log message "SSL Library Error: error:0A000126:
     SSL routines::unexpected eof while reading" when using
     OpenSSL 3 by setting SSL_OP_IGNORE_UNEXPECTED_EOF if
     available. [Rainer Jung]

  *) mod_http2: improved early cleanup of streams.
     [Stefan Eissing]

  *) mod_proxy_http2: improved error handling on connection errors while
     response is already underway.
     [Stefan Eissing]

  *) mod_http2: fixed a bug that could lead to a crash in main connection
     output handling. This occured only when the last request on a HTTP/2
     connection had been processed and the session decided to shut down.
     This could lead to an attempt to send a final GOAWAY while the previous
     write was still in progress. See PR 66646.
     [Stefan Eissing]

  *) mod_proxy_http2: fix `X-Forward-Host` header to carry the correct value.
     Fixes PR66752.
     [Stefan Eissing]

  *) mod_http2: added support for bootstrapping WebSockets via HTTP/2, as
     described in RFC 8441. A new directive 'H2WebSockets on|off' has been
     added. The feature is by default not enabled.
     As also discussed in the manual, this feature should work for setups
     using "ProxyPass backend-url upgrade=websocket" without further changes.
     Special server modules for WebSockets will have to be adapted,
     most likely, as the handling if IO events is different with HTTP/2.
     HTTP/2 WebSockets are supported on platforms with native pipes. This
     excludes Windows.
     [Stefan Eissing]

  *) mod_rewrite: Fix a regression with both a trailing ? and [QSA].
     in OCSP stapling. PR 66672. [Frank Meier <frank.meier ergon.ch>, covener]

  *) mod_http2: fixed a bug in flushing pending data on an already closed
     connection that could lead to a busy loop, preventing the HTTP/2 session
     to close down successfully. Fixed PR 66624.
     [Stefan Eissing]

  *) mod_http2: v2.0.15 with the following fixes and improvements
     - New directive 'H2EarlyHint name value' to add headers to a response,
       picked up already when a "103 Early Hints" response is sent. 'name' and
       'value' must comply to the HTTP field restrictions.
       This directive can be repeated several times and header fields of the
       same names add. Sending a 'Link' header with 'preload' relation will
       also cause a HTTP/2 PUSH if enabled and supported by the client.
     - Fixed an issue where requests were not logged and accounted in a timely
       fashion when the connection returns to "keepalive" handling, e.g. when
       the request served was the last outstanding one.
       This led to late appearance in access logs with wrong duration times
       reported.
     - Accurately report the bytes sent for a request in the '%O' Log format.
       This addresses #203, a long outstanding issue where mod_h2 has reported
       numbers over-eagerly from internal buffering and not what has actually
       been placed on the connection.
       The numbers are now the same with and without H2CopyFiles enabled.
     [Stefan Eissing]

  *) mod_proxy_http2: fix retry handling to not leak temporary errors.
     On detecting that that an existing connection was shutdown by the other
     side, a 503 response leaked even though the request was retried on a
     fresh connection.
     [Stefan Eissing]

  *) mod_rewrite: Add server directory to include path as mod_rewrite requires
     test_char.h. PR 66571 [Valeria Petrov <valeria.petrov@spinetix.com>]

  *) mod_http2: new directive `H2ProxyRequests on|off` to enable handling
     of HTTP/2 requests in a forward proxy configuration.
     General forward proxying is enabled via `ProxyRequests`. If the
     HTTP/2 protocol is also enabled for such a server/host, this new
     directive is needed in addition.
     [Stefan Eissing]

  *) core: Updated conf/mime.types:
     - .js moved from 'application/javascript' to 'text/javascript'
     - .mjs was added as 'text/javascript'
     - add .opus ('audio/ogg')
     - add 'application/vnd.geogebra.slides'
     - add WebAssembly MIME types and extension
     [Mathias Bynens <@mathiasbynens> via PR 318,
      Richard de Boer <richard tubul.net>, Dave Hodder <dmh dmh.org.uk>,
      Zbynek Konecny <zbynek1729 gmail.com>]

  *) mod_proxy_http2: fixed using the wrong "bucket_alloc" from the backend
     connection when sending data on the frontend one. This caused crashes
     or infinite loops in rare situations.
  *) mod_proxy_http2: fixed a bug in retry/response handling that could lead
     to wrong status codes or HTTP messages send at the end of response bodies
     exceeding the announced content-length.
  *) mod_proxy_http2: fix retry handling to not leak temporary errors.
     On detecting that that an existing connection was shutdown by the other
     side, a 503 response leaked even though the request was retried on a
     fresh connection.
  *) mod_http2: fixed a bug that did cleanup of consumed and pending buckets in
     the wrong order when a bucket_beam was destroyed.
     [Stefan Eissing]

  *) mod_http2: avoid double chunked-encoding on internal redirects.
     PR 66597 [Yann Ylavic, Stefan Eissing]

  *) mod_http2: Fix reporting of `Total Accesses` in server-status to not count
     HTTP/2 requests twice. Fixes PR 66801.
     [Stefan Eissing]

  *) mod_ssl: Fix handling of Certificate Revoked messages
     in OCSP stapling. PR 66626. [<gmoniker gmail.com>]

  *) mod_http2: fixed a bug in handling of stream timeouts.
     [Stefan Eissing]

  *) mod_tls: updating to rustls-ffi version 0.9.2 or higher.
     Checking in configure for proper version installed. Code
     fixes for changed clienthello member name.
     [Stefan Eissing]

  *) mod_md:
     - New directive `MDMatchNames all|servernames` to allow more control over how
       MDomains are matched to VirtualHosts.
     - New directive `MDChallengeDns01Version`. Setting this to `2` will provide
       the command also with the challenge value on `teardown` invocation. In version
       1, the default, only the `setup` invocation gets this parameter.
       Refs #312. Thanks to @domrim for the idea.
     - For Managed Domain in "manual" mode, the checks if all used ServerName and
       ServerAlias are part of the MDomain now reports a warning instead of an error
       (AH10040) when not all names are present.
     - MDChallengeDns01 can now be configured for individual domains.
       Using PR from Jérôme Billiras (@bilhackmac) and adding test case and fixing proper working
     - Fixed a bug found by Jérôme Billiras (@bilhackmac) that caused the challenge
       teardown not being invoked as it should.

  *) mod_ldap: Avoid performance overhead of APR-util rebind cache for
     OpenLDAP 2.2+.  PR 64414.  [Joe Orton]

  *) mod_http2: new directive 'H2MaxDataFrameLen n' to limit the maximum
     amount of response body bytes put into a single HTTP/2 DATA frame.
     Setting this to 0 places no limit (but the max size allowed by the
     protocol is observed).
     The module, by default, tries to use the maximum size possible, which is
     somewhat around 16KB. This sets the maximum. When less response data is
     available, smaller frames will be sent.

  *) mod_md: fixed passing of the server environment variables to programs
     started via MDMessageCmd and MDChallengeDns01 on *nix system.
     See <icing/mod_md#319>.
     [Stefan Eissing]

  *) mod_dav: Add DavBasePath directive to configure the repository root
     path.  PR 35077.  [Joe Orton]

  *) mod_alias: Add AliasPreservePath directive to map the full
     path after the alias in a location. [Graham Leggett]

  *) mod_alias: Add RedirectRelative to allow relative redirect targets to be
     issued as-is. [Eric Covener, Graham Leggett]

  *) core: Add formats %{z} and %{strftime-format} to ErrorLogFormat, and make
     sure that if the format is configured early enough it applies to every log
     line.  PR 62161.  [Yann Ylavic]

  *) mod_deflate: Add DeflateAlterETag to control how the ETag
     is modified. The 'NoChange' parameter mimics 2.2.x behavior.
     PR 45023, PR 39727. [Eric Covener]

  *) core: Optimize send_brigade_nonblocking(). [Yann Ylavic, Christophe Jaillet]

  *) mod_status: Remove duplicate keys "BusyWorkers" and "IdleWorkers".
     Resolve inconsistency between the previous two occurrences by
     counting workers in state SERVER_GRACEFUL no longer as busy,
     but instead in a new counter "GracefulWorkers" (or on HTML
     view as "workers gracefully restarting"). Also add the graceful
     counter as a new column to the existing HTML per process table
     for async MPMs. PR 63300. [Rainer Jung]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants