3

When the expiration date of MinIO links passes, It responds to an XML like this:

<Error>
  <Code>AccessDenied</Code>
  <Message>Request has expired</Message>
  <Key>key-of-the-resource</Key>
  <BucketName>bucket-name</BucketName>
  <Resource>/path-to/teh-resource</Resource>
  <RequestId>16FC78B1C6185XC7</RequestId>
  <HostId>5d405266-91b9-XXXX-ae27-c48694f203d5</HostId>
</Error>

Is there any way to customize this page by some sort of configuration inside the MinIO? I didn't find any related config on their documents.

Other potential solutions:

  • Use redirect links on my backend, and check if this link was expired, then redirect it to another page
  • Maybe we can use Nginx, but I don't know what the directives are. I appreciate your help with that.

Update

complete response headers:

$ curl <minio-url> -I

HTTP/2 403
date: Tue, 05 Jul 2022 12:51:13 GMT
content-length: 0
accept-ranges: bytes
content-security-policy: block-all-mixed-content
strict-transport-security: max-age=15724800; includeSubDomains
vary: Origin
vary: Accept-Encoding
x-amz-request-id: 16FEEFE391X98X88
x-content-type-options: nosniff
x-xss-protection: 1; mode=block

complete response:

$ curl <minio-url>

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Request has expired</Message><Key>new_structure/7553257.jpg</Key><BucketName>storage</BucketName><Resource>/decodl-storage/new_structure/7553257.jpg</Resource><RequestId>16FEEFFB573XXXXC</RequestId><HostId>5d405266-91b9-xxxx-ae27-c48694f203d5</HostId></Error>
mostafa8026
  • 273
  • 2
  • 12
  • 25
  • To apply transformations to the XML output, you can use [XSLT nginx module](http://nginx.org/en/docs/http/ngx_http_xslt_module.html). You can check an idea (transformations applied to XML autoindex output) [here](https://github.com/EvilVir/Nginx-Autoindex). If you show full MinIO response (including response headers, HTTP result code, etc.), I can help you to write an nginx location to capture this response. – Ivan Shatsky Jul 05 '22 at 11:22
  • @IvanShatsky, I updated the question. Could you please have another look? – mostafa8026 Jul 05 '22 at 13:05
  • Good news, nginx XSLT module allows to transform the response even if HTTP return code is other than 200 (I was afraid it will refuse to do it in that case). This means your question can be solved using nginx only. One more question, I don't see the `Content-Type` response header from the `HEAD` request. It should be `text/xml` in order for this solution to work; can you check the `curl -v ` output too? – Ivan Shatsky Jul 05 '22 at 20:14
  • @IvanShatsky, Output of `curl -v` is: `< content-type: application/xml`, is it ok? – mostafa8026 Jul 06 '22 at 04:18
  • 1
    It will require some additional XSLT module setup, however solution should be still workable. See the answer update. – Ivan Shatsky Jul 06 '22 at 11:37
  • May I remind you that I you don't award the bounty manually it will be just lost (only accepted answers with the score of two or more will be awarded automatically)? – Ivan Shatsky Jul 07 '22 at 08:09

2 Answers2

2

Assuming your 403 error returns with the Content-Type header being set to text/xml, you can transform this XML response to the HTML with the nginx using XSL Transformations. To do it you'll need the XSLT module, and you should be aware this module is not built by default, it should be installed additionally as a dynamic module (or enabled with the --with-http_xslt_module configuration parameter when you build nginx from the sources).

After you install the module, you should specify the xslt_stylesheet directive under the location used to proxy requests to the MinIO backend:

location ... {
    xslt_stylesheet /path/to/error.xslt;
    ...
}

Here is an example of the XSLT file that can be used to transform the XML response you've showed in your question:

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" />

<xsl:template match="/">
  <xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;</xsl:text>

  <html>
    <head>
      <title><xsl:value-of select="Error/Code"/></title>
    </head>
    <style type="text/css">
      body {
        height: 100vh;
        margin: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
      }
      p {
        font-weight: bold;
      }
      .itemvalue {
        font-family: monospace, monospace;
        font-weight: normal;
        font-size: 1em;
      }
    </style>
    <body>
      <h1><xsl:value-of select="Error/Message"/></h1>
      <p>Additional information:</p>
      <table><tbody>
        <xsl:for-each select="Error/*[not(name()='Code' or name()='Message')]">
          <tr>
            <td class="itemname"><xsl:value-of select="local-name()"/>:</td>
            <td class="itemvalue"><xsl:value-of select="."/></td>
          </tr>
        </xsl:for-each>
      </tbody></table>
    </body>
  </html>

  </xsl:template>
</xsl:stylesheet>

The above file, being applied to the response sample, will give you the following:

sample rendered HTML screenshot

You can style the output whatever you like. I think this question is not about web design (and I'm not a designer), however provided information should be enough to be an example that you can adapt to your needs.

Update

If your MinIO response comes with somethat different MIME content type, e.g. application/xml, you'd need to add that content type to the list of MIME types processed by the XSLT module with the xslt_types directive:

location ... {
    xslt_types application/xml;
    xslt_stylesheet /path/to/error.xslt;
    ...
}

Digging futher into the XSLT I finished up with somewhat different XSLT file. This one will transform only error messages containing Error top level node, leaving any other response unchanged:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/Error">
  <html>
    <head>
      <title><xsl:value-of select="./Code"/></title>
    </head>
    <style type="text/css">
      /* custom CSS styles, see the previous example */
    </style>
    <body>
      <h1><xsl:value-of select="./Message"/></h1>
      <p>Additional information:</p>
      <table><tbody>
        <xsl:for-each select="./node()[not(self::Code or self::Message)]">
          <tr>
            <td class="itemname"><xsl:value-of select="local-name()"/>:</td>
            <td class="itemvalue"><xsl:value-of select="."/></td>
          </tr>
        </xsl:for-each>
      </tbody></table>
    </body>
  </html>
</xsl:template>

<xsl:template match="/node()[not(self::Error)]">
  <xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>
Ivan Shatsky
  • 13,267
  • 2
  • 21
  • 37
0

For those who may come across this question for ingress, I've created this Dockerfile for ingress-nginx-controller; you can build it and then use your image inside the ingress-nginx-controller deployment.

FROM k8s.gcr.io/ingress-nginx/controller:v1.0.0@sha256:0851b34f69f69352bf168e6ccf30e1e20714a264ab1ecd1933e4d8c0fc3215c6 as builder
 
USER root
WORKDIR /tmp
 
RUN apk add git openssl-dev pcre-dev zlib-dev libc-dev gcc make libxml2 libxslt-dev
 
RUN NGINX_VERSION=$(nginx -v 2>&1 | sed 's/nginx version: nginx\///') && \
    wget -qO- https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar xvz && \
    mv nginx-${NGINX_VERSION} nginx
 
RUN ls ./nginx
 
RUN cd ./nginx && \
        ./configure --with-compat --with-http_xslt_module=dynamic && make modules
 
FROM k8s.gcr.io/ingress-nginx/controller:v1.0.0@sha256:0851b34f69f69352bf168e6ccf30e1e20714a264ab1ecd1933e4d8c0fc3215c6
USER root
RUN apk add libxml2 libxslt-dev
USER 101
COPY --from=builder /tmp/nginx/objs/ngx_http_xslt_filter_module.so /etc/nginx/modules

Then you can load the module with this configMap. Don't forget to restart deployment after applying the ConfigMap:

apiVersion: v1
data:
  main-snippet: |
          load_module /etc/nginx/modules/ngx_http_xslt_filter_module.so;
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx

And finally, you can use the XSLT module inside your ingresses:

metadata:
  annotations:
    ...
    nginx.ingress.kubernetes.io/configuration-snippet: |
      xslt_types application/xml;
      xslt_stylesheet /tmp/files/minio.xslt;

to mount volume you can use configmaps like this:

k -n ingress-nginx create configmap minio-xslt --from-file=</path/to/your/xslt-containing-folder>

Don't forget to update your deployment YAML file:

# k -n ingress-nginx edit deployments.apps ingress-nginx-controller
spec:
  ...
  template:
    ...
    spec:
      ...
        volumeMounts:
        ...
        - mountPath: /tmp/files
          name: minio-xslt
      ...
      volumes:
      ...
      - configMap:
          name: minio-xslt
        name: minio-xslt
mostafa8026
  • 273
  • 2
  • 12
  • 25