Repository: certbot/certbot

Issue:
No more relative path connection from live-crt to archive-crt files
I just generated a new certificate with certbot in Docker from quay.io/letsencrypt/letsencrypt with the command 
```
docker run -it --rm -p 80:80 -p 443:443 \
-v $(pwd)/le:/etc/letsencrypt \
-v $(pwd)/le/lib:/var/lib/letsencrypt \
-v $(pwd)/le/log:/var/log/letsencrypt \
quay.io/letsencrypt/letsencrypt \
certonly \
--standalone \
--agree-tos \
--rsa-key-size 4096 \
--email EMAILADDR \
example.com
``` 

Last time I generated new certificates, there were relative symlinks from the certs in ./live/example.com/ to ./archive/example.com/ (e.g. `cert.pem -> ../../archive/www.example.com/cert1.pem`) created. But with the newest version, there are absolute paths for the symlinks (e.g. `cert.pem -> /etc/letsencrypt/archive/sub.example.com/cert1.pem`). The problem here is the usage of the (absolute) directory structure of the container, but on my host machine, the folder with the config and cert files is stored (and used) in a different location. 

Are the absolute symlink paths desired behaviour and/ or is it possible to switch back to the old relative path behaviour (e.g. via an input param)? I assume, that ./live and ./archive cannot be placed too far away from each other. 

* Version: certbot 0.10.0.dev0
* Docker-Image: quay.io/letsencrypt/letsencrypt   latest              84aa8c51b7e1
* Ubuntu 14.04

To sum up: I would expect relative paths from the certs symlinks in ./live/* to the original files in ./archive/* but now there are absolute paths for the symlinks.


As I can examine, it could be a connection to https://github.com/certbot/certbot/pull/1483
```
self.namespace.config_dir = os.path.abspath(self.namespace.config_dir)
self.namespace.work_dir = os.path.abspath(self.namespace.work_dir)
self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir)
```



Discussion:
This is likely to be related to #3600, which fixes other issues we had. If you would like, you can manually edit the symlinks to be relative paths after each renewal. Alternatively, in the next version of Certbot (which you can preview by pulling master, if you're feeling risk-tolerant) you will be able to manually specify the location of the archive directory.

To do this, open the relevant renewal configuration file (probably at `/etc/letsencrypt/renewal/your_domain.com.conf`), change the value of `archive_dir`, then run `certbot update_symlinks`.
Thanks for your reply. 
Isn't https://github.com/certbot/certbot/pull/3600 just fixing tests?
For the moment, I only had the issue with generating certs - but manually editing symlinks is too much work...

I would need the certificate output in `/etc/letsencrypt/` in a relative linked and compact way (e.g. to use it in different locations like it was before, also similar to Apache's behaviour: `000-default_ssl.conf -> ../sites-available/000-default_ssl.conf`), otherwise the separated generation in Docker is quite useless for me. Second reason for this request is that I had lots of fun with absolute paths before and I'd like to prevent others from similar experience.

I had a closer look at https://github.com/certbot/certbot/pull/1483 and https://github.com/certbot/certbot/issues/836 where it looks like there was an input problem with paths from the command line. Making all paths absolute, because of this, seems like a bit overkill for me. Wouldn't it be enough (or even possible) to just convert incoming paths to absolute paths and work internally (at least inside /etc/letsencrypt) with relative paths / links? OK - With the new feature of a custom archive_dir it will not be easier to support this behaviour in the next version. I'm not that used to Python, but in Java I'm using an internal path variable and decide whether I need an absolute or relative path in the end at the specific position. Maybe a similar strategy could help here and make the config and certificates more portable between different systems and file system locations.

At the moment I'm fixing my setup with a symlink to direct the absolute paths correctly to the archive directory, but for me that does not look like a persistent fix.

Adding this to the 0.10.0 milestone so we can figure out what we're doing before it ships.
By default I'd agree that we should make relative symlinks, not absolute ones.

Resolving patch:
diff --git a/certbot/storage.py b/certbot/storage.py
--- a/certbot/storage.py
+++ b/certbot/storage.py
@@ -198,6 +198,10 @@ def lineagename_for_filename(config_filename):
             "renewal config file name must end in .conf")
     return os.path.basename(config_filename[:-len(".conf")])
 
+def _relpath_from_file(archive_dir, from_file):
+    """Path to a directory from a file"""
+    return os.path.relpath(archive_dir, os.path.dirname(from_file))
+
 
 class RenewableCert(object):
     # pylint: disable=too-many-instance-attributes,too-many-public-methods
@@ -305,6 +309,13 @@ def archive_dir(self):
             return os.path.join(
                 self.cli_config.default_archive_dir, self.lineagename)
 
+    def relative_archive_dir(self, from_file):
+        """Returns the default or specified archive directory as a relative path
+
+        Used for creating symbolic links.
+        """
+        return _relpath_from_file(self.archive_dir, from_file)
+
     @property
     def is_test_cert(self):
         """Returns true if this is a test cert from a staging server."""
@@ -331,7 +342,8 @@ def _update_symlinks(self):
         for kind in ALL_FOUR:
             link = getattr(self, kind)
             previous_link = get_link_target(link)
-            new_link = os.path.join(self.archive_dir, os.path.basename(previous_link))
+            new_link = os.path.join(self.relative_archive_dir(link),
+                os.path.basename(previous_link))
 
             os.unlink(link)
             os.symlink(new_link, link)
@@ -856,7 +868,7 @@ def new_lineage(cls, lineagename, cert, privkey, chain, cli_config):
         target = dict([(kind, os.path.join(live_dir, kind + ".pem"))
                        for kind in ALL_FOUR])
         for kind in ALL_FOUR:
-            os.symlink(os.path.join(archive, kind + "1.pem"),
+            os.symlink(os.path.join(_relpath_from_file(archive, target[kind]), kind + "1.pem"),
                        target[kind])
         with open(target["cert"], "wb") as f:
             logger.debug("Writing certificate to %s.", target["cert"])


Test patch:
diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py
--- a/certbot/tests/cert_manager_test.py
+++ b/certbot/tests/cert_manager_test.py
@@ -99,10 +99,16 @@ def test_update_live_symlinks(self):
         cert_manager.update_live_symlinks(self.cli_config)
 
         # check that symlinks go where they should
-        for domain in self.domains:
-            for kind in ALL_FOUR:
-                self.assertEqual(os.readlink(self.configs[domain][kind]),
-                    archive_paths[domain][kind])
+        prev_dir = os.getcwd()
+        try:
+            for domain in self.domains:
+                for kind in ALL_FOUR:
+                    os.chdir(os.path.dirname(self.configs[domain][kind]))
+                    self.assertEqual(
+                        os.path.realpath(os.readlink(self.configs[domain][kind])),
+                        os.path.realpath(archive_paths[domain][kind]))
+        finally:
+            os.chdir(prev_dir)
 
 
 class CertificatesTest(BaseCertManagerTest):