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    if (LOG.isDebugEnabled()) {
069      LOG.debug("using '" + file + "' for output stream.");
070    }
071    FileOutputStream out = new FileOutputStream(file);
072    return out;
073  }
074
075  @Override
076  protected boolean keystoreExists() throws IOException {
077    /* The keystore loader doesn't handle zero length files. */
078    return file.exists() && (file.length() > 0);
079  }
080
081  @Override
082  protected InputStream getInputStreamForFile() throws IOException {
083    FileInputStream is = new FileInputStream(file);
084    return is;
085  }
086
087  @Override
088  protected void createPermissions(String perms) throws IOException {
089    int mode = 700;
090    try {
091      mode = Integer.parseInt(perms, 8);
092    } catch (NumberFormatException nfe) {
093      throw new IOException("Invalid permissions mode provided while "
094          + "trying to createPermissions", nfe);
095    }
096    permissions = modeToPosixFilePermission(mode);
097  }
098
099  @Override
100  protected void stashOriginalFilePermissions() throws IOException {
101    // save off permissions in case we need to
102    // rewrite the keystore in flush()
103    if (!Shell.WINDOWS) {
104      Path path = Paths.get(file.getCanonicalPath());
105      permissions = Files.getPosixFilePermissions(path);
106    } else {
107      // On Windows, the JDK does not support the POSIX file permission APIs.
108      // Instead, we can do a winutils call and translate.
109      String[] cmd = Shell.getGetPermissionCommand();
110      String[] args = new String[cmd.length + 1];
111      System.arraycopy(cmd, 0, args, 0, cmd.length);
112      args[cmd.length] = file.getCanonicalPath();
113      String out = Shell.execCommand(args);
114      StringTokenizer t = new StringTokenizer(out, Shell.TOKEN_SEPARATOR_REGEX);
115      // The winutils output consists of 10 characters because of the leading
116      // directory indicator, i.e. "drwx------".  The JDK parsing method expects
117      // a 9-character string, so remove the leading character.
118      String permString = t.nextToken().substring(1);
119      permissions = PosixFilePermissions.fromString(permString);
120    }
121  }
122
123  @Override
124  protected void initFileSystem(URI uri)
125      throws IOException {
126    super.initFileSystem(uri);
127    try {
128      file = new File(new URI(getPath().toString()));
129      if (LOG.isDebugEnabled()) {
130        LOG.debug("initialized local file as '" + file + "'.");
131        if (file.exists()) {
132          LOG.debug("the local file exists and is size " + file.length());
133          if (LOG.isTraceEnabled()) {
134            if (file.canRead()) {
135              LOG.trace("we can read the local file.");
136            }
137            if (file.canWrite()) {
138              LOG.trace("we can write the local file.");
139            }
140          }
141        } else {
142          LOG.debug("the local file does not exist.");
143        }
144      }
145    } catch (URISyntaxException e) {
146      throw new IOException(e);
147    }
148  }
149
150  @Override
151  public void flush() throws IOException {
152    super.flush();
153    if (LOG.isDebugEnabled()) {
154      LOG.debug("Resetting permissions to '" + permissions + "'");
155    }
156    if (!Shell.WINDOWS) {
157      Files.setPosixFilePermissions(Paths.get(file.getCanonicalPath()),
158          permissions);
159    } else {
160      // FsPermission expects a 10-character string because of the leading
161      // directory indicator, i.e. "drwx------". The JDK toString method returns
162      // a 9-character string, so prepend a leading character.
163      FsPermission fsPermission = FsPermission.valueOf(
164          "-" + PosixFilePermissions.toString(permissions));
165      FileUtil.setPermission(file, fsPermission);
166    }
167  }
168
169  /**
170   * The factory to create JksProviders, which is used by the ServiceLoader.
171   */
172  public static class Factory extends CredentialProviderFactory {
173    @Override
174    public CredentialProvider createProvider(URI providerName,
175        Configuration conf) throws IOException {
176      if (SCHEME_NAME.equals(providerName.getScheme())) {
177        return new LocalJavaKeyStoreProvider(providerName, conf);
178      }
179      return null;
180    }
181  }
182
183  private static Set<PosixFilePermission> modeToPosixFilePermission(
184      int mode) {
185    Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class);
186    if ((mode & 0001) != 0) {
187      perms.add(PosixFilePermission.OTHERS_EXECUTE);
188    }
189    if ((mode & 0002) != 0) {
190      perms.add(PosixFilePermission.OTHERS_WRITE);
191    }
192    if ((mode & 0004) != 0) {
193      perms.add(PosixFilePermission.OTHERS_READ);
194    }
195    if ((mode & 0010) != 0) {
196      perms.add(PosixFilePermission.GROUP_EXECUTE);
197    }
198    if ((mode & 0020) != 0) {
199      perms.add(PosixFilePermission.GROUP_WRITE);
200    }
201    if ((mode & 0040) != 0) {
202      perms.add(PosixFilePermission.GROUP_READ);
203    }
204    if ((mode & 0100) != 0) {
205      perms.add(PosixFilePermission.OWNER_EXECUTE);
206    }
207    if ((mode & 0200) != 0) {
208      perms.add(PosixFilePermission.OWNER_WRITE);
209    }
210    if ((mode & 0400) != 0) {
211      perms.add(PosixFilePermission.OWNER_READ);
212    }
213    return perms;
214  }
215}