summaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/fs/ContainerUserPrincipalLookupService.java
blob: 893e86ca2398fc749601af446dab360cd79d7264 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.fs;

import com.google.common.collect.ImmutableBiMap;

import java.io.IOException;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.attribute.UserPrincipalNotFoundException;
import java.util.Objects;
import java.util.Optional;

/**
 * @author valerijf
 */
class ContainerUserPrincipalLookupService extends UserPrincipalLookupService {

    /** Total number of UID/GID that are mapped for each container */
    private static final int ID_RANGE = 1 << 16;

    /**
     * IDs outside the ID range are translated to the overflow ID before being written to disk:
     * https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/Documentation/admin-guide/sysctl/fs.rst#overflowgid--overflowuid */
    static final int OVERFLOW_ID = 65_534;

    private static final ImmutableBiMap<String, Integer> CONTAINER_IDS_BY_NAME = ImmutableBiMap.<String, Integer>builder()
            .put("root", 0)
            .put("vespa", 1000)
            .build();

    private final UserPrincipalLookupService baseFsUserPrincipalLookupService;
    private final int uidOffset;
    private final int gidOffset;

    ContainerUserPrincipalLookupService(UserPrincipalLookupService baseFsUserPrincipalLookupService, int uidOffset, int gidOffset) {
        this.baseFsUserPrincipalLookupService = baseFsUserPrincipalLookupService;
        this.uidOffset = uidOffset;
        this.gidOffset = gidOffset;
    }

    public int containerUidToHostUid(int containerUid) { return containerIdToHostId(containerUid, uidOffset); }
    public int containerGidToHostGid(int containerGid) { return containerIdToHostId(containerGid, gidOffset); }
    public int hostUidToContainerUid(int hostUid) { return hostIdToContainerId(hostUid, uidOffset); }
    public int hostGidToContainerGid(int hostGid) { return hostIdToContainerId(hostGid, gidOffset); }

    @Override
    public ContainerUserPrincipal lookupPrincipalByName(String name) throws IOException {
        int containerUid = resolve(name);
        String hostUid = String.valueOf(containerUidToHostUid(containerUid));
        return new ContainerUserPrincipal(containerUid, baseFsUserPrincipalLookupService.lookupPrincipalByName(hostUid));
    }

    @Override
    public ContainerGroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
        int containerGid = resolve(group);
        String hostGid = String.valueOf(containerGidToHostGid(containerGid));
        return new ContainerGroupPrincipal(containerGid, baseFsUserPrincipalLookupService.lookupPrincipalByGroupName(hostGid));
    }

    private static int resolve(String name) throws UserPrincipalNotFoundException {
        Integer id = CONTAINER_IDS_BY_NAME.get(name);
        if (id != null) return id;

        try {
            return Integer.parseInt(name);
        } catch (NumberFormatException ignored) {
            throw new UserPrincipalNotFoundException(name);
        }
    }

    private abstract static class NamedPrincipal implements UserPrincipal {
        private final int id;
        private final String name;
        private final UserPrincipal baseFsPrincipal;

        private NamedPrincipal(int id, UserPrincipal baseFsPrincipal) {
            this.id = id;
            this.name = Optional.ofNullable(CONTAINER_IDS_BY_NAME.inverse().get(id)).orElseGet(() -> Integer.toString(id));
            this.baseFsPrincipal = Objects.requireNonNull(baseFsPrincipal);
        }

        @Override
        public final String getName() {
            return name;
        }

        public int id() {
            return id;
        }

        public UserPrincipal baseFsPrincipal() {
            return baseFsPrincipal;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            NamedPrincipal that = (NamedPrincipal) o;
            return id == that.id && baseFsPrincipal.equals(that.baseFsPrincipal);
        }

        @Override
        public int hashCode() {
            return Objects.hash(id, baseFsPrincipal);
        }

        @Override
        public String toString() {
            return "{id=" + id + ", baseFsPrincipal=" + baseFsPrincipal + '}';
        }
    }

    static final class ContainerUserPrincipal extends NamedPrincipal {
        ContainerUserPrincipal(int id, UserPrincipal baseFsPrincipal) { super(id, baseFsPrincipal); }
    }

    static final class ContainerGroupPrincipal extends NamedPrincipal implements GroupPrincipal {
        ContainerGroupPrincipal(int id, GroupPrincipal baseFsPrincipal) { super(id, baseFsPrincipal); }

        @Override public GroupPrincipal baseFsPrincipal() { return (GroupPrincipal) super.baseFsPrincipal(); }
    }

    private static int containerIdToHostId(int id, int idOffset) {
        if (id < 0 || id > ID_RANGE)
            throw new IllegalArgumentException("Invalid container id: " + id);
        return idOffset + id;
    }

    private static int hostIdToContainerId(int id, int idOffset) {
        id = id - idOffset;
        return id < 0 || id >= ID_RANGE ? OVERFLOW_ID : id;
    }
}