001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.security.alias;
020
021import org.apache.hadoop.classification.InterfaceAudience;
022import org.apache.hadoop.conf.Configuration;
023import org.apache.hadoop.fs.FileUtil;
024import org.apache.hadoop.fs.permission.FsPermission;
025import org.apache.hadoop.util.Shell;
026
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.FileOutputStream;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.OutputStream;
033import java.net.URI;
034import java.net.URISyntaxException;
035import java.nio.file.Files;
036import java.nio.file.Path;
037import java.nio.file.Paths;
038import java.nio.file.attribute.PosixFilePermission;
039import java.nio.file.attribute.PosixFilePermissions;
040import java.util.Set;
041import java.util.StringTokenizer;
042import java.util.EnumSet;
043
044/**
045 * CredentialProvider based on Java's KeyStore file format. The file may be
046 * stored only on the local filesystem using the following name mangling:
047 * localjceks://file/home/larry/creds.jceks -> file:///home/larry/creds.jceks
048 */
049@InterfaceAudience.Private
050public final class LocalJavaKeyStoreProvider extends
051    AbstractJavaKeyStoreProvider {
052  public static final String SCHEME_NAME = "localjceks";
053  private File file;
054  private Set<PosixFilePermission> permissions;
055
056  private LocalJavaKeyStoreProvider(URI uri, Configuration conf)
057      throws IOException {
058    super(uri, conf);
059  }
060
061  @Override
062  protected String getSchemeName() {
063    return SCHEME_NAME;
064  }
065
066  @Override
067  protected OutputStream getOutputStreamForKeystore() throws IOException {
068    FileOutputStream out = new FileOutputStream(file);
069    return out;
070  }
071
072  @Override
073  protected boolean keystoreExists() throws IOException {
074    return file.exists();
075  }
076
077  @Override
078  protected InputStream getInputStreamForFile() throws IOException {
079    FileInputStream is = new FileInputStream(file);
080    return is;
081  }
082
083  @Override
084  protected void createPermissions(String perms) throws IOException {
085    int mode = 700;
086    try {
087      mode = Integer.parseInt(perms, 8);
088    } catch (NumberFormatException nfe) {
089      throw new IOException("Invalid permissions mode provided while "
090          + "trying to createPermissions", nfe);
091    }
092    permissions = modeToPosixFilePermission(mode);
093  }
094
095  @Override
096  protected void stashOriginalFilePermissions() throws IOException {
097    // save off permissions in case we need to
098    // rewrite the keystore in flush()
099    if (!Shell.WINDOWS) {
100      Path path = Paths.get(file.getCanonicalPath());
101      permissions = Files.getPosixFilePermissions(path);
102    } else {
103      // On Windows, the JDK does not support the POSIX file permission APIs.
104      // Instead, we can do a winutils call and translate.
105      String[] cmd = Shell.getGetPermissionCommand();
106      String[] args = new String[cmd.length + 1];
107      System.arraycopy(cmd, 0, args, 0, cmd.length);
108      args[cmd.length] = file.getCanonicalPath();
109      String out = Shell.execCommand(args);
110      StringTokenizer t = new StringTokenizer(out, Shell.TOKEN_SEPARATOR_REGEX);
111      // The winutils output consists of 10 characters because of the leading
112      // directory indicator, i.e. "drwx------".  The JDK parsing method expects
113      // a 9-character string, so remove the leading character.
114      String permString = t.nextToken().substring(1);
115      permissions = PosixFilePermissions.fromString(permString);
116    }
117  }
118
119  @Override
120  protected void initFileSystem(URI uri, Configuration conf)
121      throws IOException {
122    super.initFileSystem(uri, conf);
123    try {
124      file = new File(new URI(getPath().toString()));
125    } catch (URISyntaxException e) {
126      throw new IOException(e);
127    }
128  }
129
130  @Override
131  public void flush() throws IOException {
132    super.flush();
133    if (!Shell.WINDOWS) {
134      Files.setPosixFilePermissions(Paths.get(file.getCanonicalPath()),
135          permissions);
136    } else {
137      // FsPermission expects a 10-character string because of the leading
138      // directory indicator, i.e. "drwx------". The JDK toString method returns
139      // a 9-character string, so prepend a leading character.
140      FsPermission fsPermission = FsPermission.valueOf(
141          "-" + PosixFilePermissions.toString(permissions));
142      FileUtil.setPermission(file, fsPermission);
143    }
144  }
145
146  /**
147   * The factory to create JksProviders, which is used by the ServiceLoader.
148   */
149  public static class Factory extends CredentialProviderFactory {
150    @Override
151    public CredentialProvider createProvider(URI providerName,
152        Configuration conf) throws IOException {
153      if (SCHEME_NAME.equals(providerName.getScheme())) {
154        return new LocalJavaKeyStoreProvider(providerName, conf);
155      }
156      return null;
157    }
158  }
159
160  private static Set<PosixFilePermission> modeToPosixFilePermission(
161      int mode) {
162    Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class);
163    if ((mode & 0001) != 0) {
164      perms.add(PosixFilePermission.OTHERS_EXECUTE);
165    }
166    if ((mode & 0002) != 0) {
167      perms.add(PosixFilePermission.OTHERS_WRITE);
168    }
169    if ((mode & 0004) != 0) {
170      perms.add(PosixFilePermission.OTHERS_READ);
171    }
172    if ((mode & 0010) != 0) {
173      perms.add(PosixFilePermission.GROUP_EXECUTE);
174    }
175    if ((mode & 0020) != 0) {
176      perms.add(PosixFilePermission.GROUP_WRITE);
177    }
178    if ((mode & 0040) != 0) {
179      perms.add(PosixFilePermission.GROUP_READ);
180    }
181    if ((mode & 0100) != 0) {
182      perms.add(PosixFilePermission.OWNER_EXECUTE);
183    }
184    if ((mode & 0200) != 0) {
185      perms.add(PosixFilePermission.OWNER_WRITE);
186    }
187    if ((mode & 0400) != 0) {
188      perms.add(PosixFilePermission.OWNER_READ);
189    }
190    return perms;
191  }
192}