]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsSecurity.java
7e1834a441826e3ebf6316f7c5768e0d85b29d58
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / CmsSecurity.java
1 package org.argeo.cms.internal.kernel;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.Inet6Address;
6 import java.net.InetAddress;
7 import java.net.URL;
8 import java.net.UnknownHostException;
9 import java.nio.file.Files;
10 import java.nio.file.Path;
11 import java.security.PrivilegedExceptionAction;
12 import java.util.Iterator;
13
14 import javax.security.auth.Subject;
15 import javax.security.auth.callback.Callback;
16 import javax.security.auth.callback.CallbackHandler;
17 import javax.security.auth.callback.NameCallback;
18 import javax.security.auth.callback.UnsupportedCallbackException;
19 import javax.security.auth.kerberos.KerberosPrincipal;
20 import javax.security.auth.login.Configuration;
21 import javax.security.auth.login.LoginContext;
22 import javax.security.auth.login.LoginException;
23
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.argeo.cms.CmsException;
27 import org.argeo.naming.DnsBrowser;
28 import org.argeo.node.NodeConstants;
29 import org.ietf.jgss.GSSCredential;
30 import org.ietf.jgss.GSSException;
31 import org.ietf.jgss.GSSManager;
32 import org.ietf.jgss.GSSName;
33 import org.ietf.jgss.Oid;
34
35 /** Low-level kernel security */
36 class CmsSecurity implements KernelConstants {
37 private final static Log log = LogFactory.getLog(CmsSecurity.class);
38 // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
39 private final static Oid KERBEROS_OID;
40 static {
41 try {
42 KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
43 } catch (GSSException e) {
44 throw new IllegalStateException("Cannot create Kerberos OID", e);
45 }
46 }
47
48 public final static int DEPLOYED = 30;
49 public final static int STANDALONE = 20;
50 public final static int DEV = 10;
51 public final static int UNKNOWN = 0;
52
53 private String hostname;
54
55 private final int securityLevel;
56 private Subject nodeSubject;
57
58 // IPA
59 private String kerberosDomain;
60 private String service = null;
61 private GSSCredential acceptorCredentials;
62
63 private Path nodeKeyTab = KernelUtils.getOsgiInstancePath("node/krb5.keytab");
64 private File keyStoreFile;
65
66 public CmsSecurity() {
67 if (!DeployConfig.isInitialized()) // first init
68 FirstInit.prepareInstanceArea();
69
70 securityLevel = evaluateSecurityLevel();
71 // Configure JAAS first
72 if (System.getProperty(JAAS_CONFIG_PROP) == null) {
73 String jaasConfig = securityLevel < DEPLOYED ? JAAS_CONFIG : JAAS_CONFIG_IPA;
74 URL url = getClass().getClassLoader().getResource(jaasConfig);
75 System.setProperty(JAAS_CONFIG_PROP, url.toExternalForm());
76 }
77 // explicitly load JAAS configuration
78 Configuration.getConfiguration();
79 nodeSubject = logInKernel();
80
81 // firstInit = !new File(getOsgiInstanceDir(), DIR_NODE).exists();
82
83 // this.keyStoreFile = new File(KernelUtils.getOsgiInstanceDir(),
84 // "node.p12");
85 // createKeyStoreIfNeeded();
86 }
87
88 private int evaluateSecurityLevel() {
89 int res = UNKNOWN;
90 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
91 InetAddress localhost = InetAddress.getLocalHost();
92 hostname = localhost.getHostName();
93 String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
94 String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
95 boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
96 kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
97 if (consistentIp && kerberosDomain != null && Files.exists(nodeKeyTab)) {
98 res = DEPLOYED;
99 } else {
100 res = STANDALONE;
101 kerberosDomain = null;
102 // FIXME make state more robust
103 }
104 } catch (UnknownHostException e) {
105 hostname = "localhost";
106 log.warn("Cannot determine hostname, using " + hostname + ":" + e.getMessage());
107 res = STANDALONE;
108 } catch (Exception e) {
109 log.warn("Exception when evaluating security level, setting it to DEV", e);
110 res = DEV;
111 }
112
113 if (res == UNKNOWN)
114 throw new CmsException("Undefined security level");
115 return res;
116 }
117
118 private Subject logInKernel() {
119 final Subject nodeSubject = new Subject();
120
121 CallbackHandler callbackHandler;
122 if (Files.exists(nodeKeyTab)) {
123 service = NodeConstants.NODE_SERVICE;
124 callbackHandler = new CallbackHandler() {
125
126 @Override
127 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
128 for (Callback callback : callbacks)
129 if (callback instanceof NameCallback)
130 ((NameCallback) callback).setName(getKerberosServicePrincipal());
131
132 }
133 };
134 try {
135 LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, nodeSubject,
136 callbackHandler);
137 kernelLc.login();
138 } catch (LoginException e) {
139 throw new CmsException("Cannot log in kernel", e);
140 }
141 } else {
142 callbackHandler = null;
143 // try {
144 // callbackHandler = (CallbackHandler)
145 // Class.forName("com.sun.security.auth.callback.TextCallbackHandler")
146 // .newInstance();
147 // } catch (ReflectiveOperationException e) {
148 // throw new CmsException("Cannot create text callback handler", e);
149 // }
150 try {
151 LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_SINGLE_USER, nodeSubject);
152 kernelLc.login();
153 } catch (LoginException e) {
154 throw new CmsException("Cannot log in kernel", e);
155 }
156 }
157
158 if (securityLevel >= DEPLOYED) {
159 acceptorCredentials = logInAsAcceptor(nodeSubject);
160 }
161 return nodeSubject;
162 }
163
164 private String getKerberosServicePrincipal() {
165 if (hostname == null || "locahost".equals(hostname) || kerberosDomain == null || service == null)
166 throw new IllegalStateException("Cannot determine kerberos principal");
167 return service + "/" + hostname + "@" + kerberosDomain;
168 }
169
170 private GSSCredential logInAsAcceptor(Subject nodeSubject) {
171 // GSS
172 Iterator<KerberosPrincipal> krb5It = nodeSubject.getPrincipals(KerberosPrincipal.class).iterator();
173 if (!krb5It.hasNext())
174 return null;
175 KerberosPrincipal krb5Principal = null;
176 while (krb5It.hasNext()) {
177 KerberosPrincipal principal = krb5It.next();
178 if (service == null && krb5Principal == null)// first as default
179 krb5Principal = principal;
180 if (service != null && principal.getName().equals(getKerberosServicePrincipal()))
181 krb5Principal = principal;
182 }
183
184 if (krb5Principal == null)
185 return null;
186
187 GSSManager manager = GSSManager.getInstance();
188 try {
189 GSSName gssName = manager.createName(krb5Principal.getName(), null);
190 GSSCredential serverCredentials = Subject.doAs(nodeSubject, new PrivilegedExceptionAction<GSSCredential>() {
191
192 @Override
193 public GSSCredential run() throws GSSException {
194 return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
195 GSSCredential.ACCEPT_ONLY);
196
197 }
198
199 });
200 if (log.isDebugEnabled())
201 log.debug("GSS acceptor configured for " + krb5Principal);
202 return serverCredentials;
203 } catch (Exception gsse) {
204 throw new CmsException("Cannot create acceptor credentials for " + krb5Principal, gsse);
205 }
206 }
207 //
208 // private Subject logInHardenedKernel() {
209 // final Subject kernelSubject = new Subject();
210 // createKeyStoreIfNeeded();
211 //
212 // CallbackHandler cbHandler = new CallbackHandler() {
213 //
214 // @Override
215 // public void handle(Callback[] callbacks) throws IOException,
216 // UnsupportedCallbackException {
217 // // alias
218 //// ((NameCallback) callbacks[1]).setName(AuthConstants.ROLE_KERNEL);
219 // // store pwd
220 // ((PasswordCallback) callbacks[2]).setPassword("changeit".toCharArray());
221 // // key pwd
222 // ((PasswordCallback) callbacks[3]).setPassword("changeit".toCharArray());
223 // }
224 // };
225 // try {
226 // LoginContext kernelLc = new
227 // LoginContext(KernelConstants.LOGIN_CONTEXT_HARDENED_KERNEL,
228 // kernelSubject,
229 // cbHandler);
230 // kernelLc.login();
231 // } catch (LoginException e) {
232 // throw new CmsException("Cannot log in kernel", e);
233 // }
234 // return kernelSubject;
235 // }
236
237 void destroy() {
238 // Logout kernel
239 try {
240 LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, nodeSubject);
241 kernelLc.logout();
242 } catch (LoginException e) {
243 throw new CmsException("Cannot log out kernel", e);
244 }
245
246 // Security.removeProvider(SECURITY_PROVIDER);
247 }
248
249 File getNodeKeyStore() {
250 return keyStoreFile;
251 }
252
253 public synchronized int getSecurityLevel() {
254 return securityLevel;
255 }
256
257 public String getKerberosDomain() {
258 return kerberosDomain;
259 }
260
261 public Subject getNodeSubject() {
262 return nodeSubject;
263 }
264
265 public GSSCredential getServerCredentials() {
266 return acceptorCredentials;
267 }
268
269 // public void setSecurityLevel(int newValue) {
270 // if (newValue != STANDALONE || newValue != DEV)
271 // throw new CmsException("Invalid value for security level " + newValue);
272 // if (newValue >= securityLevel)
273 // throw new CmsException(
274 // "Impossible to increase security level (from " + securityLevel + " to " +
275 // newValue + ")");
276 // securityLevel = newValue;
277 // }
278
279 // private void createKeyStoreIfNeeded() {
280 // // for (Provider provider : Security.getProviders())
281 // // System.out.println(provider.getName());
282 //
283 // char[] ksPwd = "changeit".toCharArray();
284 // char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length);
285 // if (!keyStoreFile.exists()) {
286 // try {
287 // keyStoreFile.getParentFile().mkdirs();
288 // KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd);
289 // // PkiUtils.generateSelfSignedCertificate(keyStore, new
290 // // X500Principal(AuthConstants.ROLE_KERNEL), 1024,
291 // // keyPwd);
292 // PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore);
293 // if (log.isDebugEnabled())
294 // log.debug("Created keystore " + keyStoreFile);
295 // } catch (Exception e) {
296 // if (keyStoreFile.length() == 0)
297 // keyStoreFile.delete();
298 // log.error("Cannot create keystore " + keyStoreFile, e);
299 // }
300 // }
301 // }
302
303 // private final static String SECURITY_PROVIDER = "BC";// Bouncy Castle
304 // private final static Log log;
305 // static {
306 // log = LogFactory.getLog(NodeSecurity.class);
307 // // Make Bouncy Castle the default provider
308 // Provider provider = new BouncyCastleProvider();
309 // int position = Security.insertProviderAt(provider, 1);
310 // if (position == -1)
311 // log.error("Provider " + provider.getName()
312 // + " already installed and could not be set as default");
313 // Provider defaultProvider = Security.getProviders()[0];
314 // if (!defaultProvider.getName().equals(SECURITY_PROVIDER))
315 // log.error("Provider name is " + defaultProvider.getName()
316 // + " but it should be " + SECURITY_PROVIDER);
317 // }
318 }